Na webu jsou dokumenty různých typů. Pro zjednodušení nás však nyní zajímají jen HTML a RSS dokumenty. Tuto skutečnost popíšeme třemi třídami. Jedna je abstraktní a popisuje to, co je pro HTML a RSS společné, další dvě pak popisují specifické vlastnosti daného dokumentu. Co by přesně popisovaly, není podstatné, jde mi o to nastínit situaci, ve které dává smysl použití dědičnosti.
public abstract class Document {
public Document(Uri url) {
Url = url;
}
public Uri Url { get; private set; }
}
public class HtmlDocument : Document {
public HtmlDocument(Uri url) : base(url) { }
}
public class RssDocument : Document {
public RssDocument(Uri url) : base(url) { }
}
Všechny dokumenty mají společnou jednu věc – URL adresu – jednoznačný identifikátor na webu. Řekněme, že třídu HtmlDocument používáme ke zpracování nějakých dat a zajímá nás, nebo spíše chceme ovlivnit chování vzhledem k datovým strukturám, jako je List<T>. Vyjděme z výchozího chování.
var list = new List<Document>();
list.Add(new HtmlDocument(new Uri("http://dajbych.net")));
var result = list.Contains(new HtmlDocument(new Uri("http://dajbych.net")));
Hodnota proměnné result je false. V programu se vytvoří dvě třídy, dvě odlišné reference a u nich object.ReferenceEquals vrací false.
Když chceme docílit toho, aby se .NET zajímal nejen o shodnost referencí, ale i shodnost dat, implementujeme třídě Document rozhraní IEquatable<T>:
public abstract class Document : IEquatable<Document> {
public Document(Uri url) {
Url = url;
}
public Uri Url { get; private set; }
public bool Equals(Document other) {
if (this.Url != null && other.Url != null) {
return this.Url.Equals(other.Url);
} else {
return base.Equals(other);
}
}
}
Potom už se bude List<T> chovat odlišně:
var list = new List<Document>();
list.Add(new HtmlDocument(new Uri("http://dajbych.net")));
var result = list.Contains(new HtmlDocument(new Uri("http://dajbych.net")));
Hodnota proměnné result je nyní true. Prima, základní problém je vyřešen. Jenže k praktické použitelnosti má tento kód ještě hodně daleko.
Tak především Document je pouze bázová třída. Po vyjmutí z List<T> chci ale pracovat s třídou HtmlDocument. Přetypování je zdrojem chyb. Chci vlastně List<HtmlDocument>. Na List<Document> jsem slevil jen jako ústupek IEquatable<Document>. Není to špatně?
var list = new List<HtmlDocument>();
list.Add(new HtmlDocument(new Uri("http://feedviewer.net")));
var result = list.Contains(new HtmlDocument(new Uri("http://feedviewer.net")));
Hodnota proměnné result je false, protože .NET nehledá IEquatable<T> v bázových třídách. Tuto dědičnost je třeba doprogramovat:
public class HtmlDocument : Document, IEquatable<HtmlDocument> {
public HtmlDocument(Uri url) : base(url) { }
public bool Equals(HtmlDocument other) {
return base.Equals(other);
}
}
Teď už se vše chová tak, jak očekáváme.
var list = new List<HtmlDocument>();
list.Add(new HtmlDocument(new Uri("http://feedviewer.net")));
var result = list.Contains(new HtmlDocument(new Uri("http://feedviewer.net")));
Hodnota proměnné result je true. Logika porovnávání je v bázové třídě a společná pro všechny potomky.