Dajbych.net


Genericita, rozhraní a dědičnost

, 3 minuty čtení

net2010 logo

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://dajbych.net")));
var result = list.Contains(new HtmlDocument(new Uri("http://dajbych.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.