Przykład kodu obiektowego:
interface IShape { double Area(); } class Square: IShape { private Point topLeft; private double side; public double Area() { return side*side; } } class Circle: IShape { private Point center; private double radius; public double Area() { return Math.PI*radius*radius; } }
Przykład kodu używającego struktur danych:
class Square { public Point TopLeft; public double Side; } class Circle { public Point Center; public double Radius; } class AreaCalculator { public double Area(object shape) { if(shape is Square) { var s = (Square)shape; return s.Side * s.Side; } if(shape is Circle) { var c = (Circle)shape; return Math.PI * c.Radius * c.Radius; } throw new NoSuchShapeException(); } }Autor zauważa przy tym, że łatwo jest dodać nową klasę obiektu albo nową operację na strukturach danych (wystarczy dopisać nowy kod, a w starym nie trzeba wprowadzać żadnych zmian), natomiast trudno jest dodać nową metodę obiektów albo nowy typ struktury danych (stary kod trzeba będzie zmienić w każdym miejscu). Ponadto zauważa, że gdy używa się hybrydowych typów, które mają jawną implementację (gettery i settery) oraz zbiór metod, to mamy kod posiadający wady obu styli programowania - wtedy jakakolwiek zmiana jest utrudniona.
Trudno nie zgodzić się z Robertem Martinem. Chciałbym do tego wszystkiego dorzucić jeszcze swoje trzy grosze. Zwróćmy uwagę, że istnieje wiele sytuacji, gdy ukrycie implementacji jest mniej naturalnym rozwiązaniem niż jej ujawnienie. W sytuacjach, gdy potrzeba funkcjonalności np.:
- przechowania danych odczytanych z bazy,
- przechowania danych odczytanych z XMLa,
- przechowywania danych wyświetlanych użytkownikowi na formularzu,
- przesyłania danych przez sieć,
- itp.
ujawnienie implementacji jest co najmniej bardziej naturalne, jeśli nie konieczne. Gdy korzystamy z mechanizmów takich, jak serializacja XML, to sam mechanizm wymusza na nas ujawnienie implementacji - wszystkie zmienne instancyjne muszą wtedy mieć gettery i settery. I nie łudźmy się w tym miejscu, że gettery / settery ukrywają implementację - to nie prawda. Dam konia z rzędem temu, kto powie mi, jaką cechą istotną z punktu widzenia projektowania różni się kod:
class Person { public string FirstName { get; set; } public string LastName { get; set; } }od kodu:
class Person { public string FirstName; public string LastName; }Widzicie jakąś różnicę? Ja nie widzę (i nie ma tutaj znaczenia, że w przykładzie powyżej użyłem właściwości automatycznych, a nie zwyczajnych z prywatnym polem pod spodem - to nic nie zmienia - implementacja jest ujawniona).
Jaki praktyczny wniosek płynie z powyższych obserwacji? Według mnie, po pierwsze taki, że nie należy kurczowo trzymać się żadnego z góry określonego paradygmatu, tylko dostosować swój styl programowania do danego kontekstu. Jak pokazuje praktyka, nie każdy kontekst zachęca do stylu obiektowego. Ale gdy w danym module aplikacji obierze się dany styl, to należy być konsekwentnym i nie należy mieszać dwóch różnych styli (ani oszukiwać się, że klasa w stylu bean jest obiektowa).