poniedziałek, 20 lutego 2012

Obiekty vs. struktury danych

    Jestem właśnie w trakcie lektury "Czystego kodu" Roberta C. Martina (polecam wszystkim programistom). Chciałbym podzielić się pewnym spostrzeżeniem autora tej książki. Otóż jako zupełne przeciwieństwa stawia on kod obiektowy oraz kod proceduralny operujący na strukturach danych. Zasadnicza różnica polega na tym, że klasy ukrywają swoją implementację komunikując się ze światem zewnętrznym wyłącznie przez metody, podczas gdy struktury danych mają zupełnie jawną postać, przez co nie potrzebują żadnych metod.
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).

4 komentarze:

  1. Hmm.... hybrydowe - skąd ja to znam :p

    OdpowiedzUsuń
  2. Z tym koniem z rzędem, to trochę się zapędziłem :-) Gdy to pisałem, myślałem o ujawnianiu implementacji, a nie pomyślałem o tym, że czasami przydatne jest, gdy właściwość implementuje jakiś interfejs - a tego nie potrafią pola. Trzeba to uznać za różnicę z punktu widzenia projektowania.

    OdpowiedzUsuń
  3. Obiektowość klas fajnie jest zasotosowana w https://www.cashbill.pl/integracja/integracja-platnosci-online-wpecommerce Jest to bardzo dobrze napisany skrypt, wolny o wszelakich błędów.

    OdpowiedzUsuń
  4. kolejny blog programistyczny który dobrze się zapowiadał ale niestety nie przeszedł próby czasu, cóż widocznie cierpliwość nie jest narodową cechą naszych programistów

    OdpowiedzUsuń