Hater Series: Object oriented design in C#

W pracy najczęściej zdarza mi się pisać kod w C#.

Jest to bardzo wygodny język, posiada wiele konstrukcji ułatwiających pisanie kodu (generic, lambda, linq itp).
Jest też dojrzały, posiada całą masę bibliotek wszelakich oraz powoli dąży do bycia multiplatformowym i open-source.

Jednakże najważniejsze w nim jest to, że posiada silne typowanie oraz jest zorientowany obiektowo.

Jak wiadomo, silne typowanie pozwala wychwytywać błędy programisty już na poziomie kompilacji. Literówki, źle podane argumenty itp..

Obiektowość z kolei ułatwia projektowanie aplikacji oraz ich utrzymywanie. C# obiektowość udostępnia poprzez klasy – pozwala to rozdzielać kod na mniejsze kawałki które potem mogą być od siebie uniezależniane. Obiektowość wykorzystana poprawnie daje programiście większą reużywalność kodu oraz zmniejsza ryzyko błędów.

Kłamstwo

Wszystko fajnie, ale niestety obiektowość w C# jest ułomna – wzorem Javy posiada słowo kluczowe interface.

A co złego jest w interfejsach?

Same interfejsy nie są złe – przyczyna ich istnienia jest zła.

Hmm, może inaczej.
Czym różni się klasa abstrakcyjna od interfejsu?

No, interfejs może mieć tylko publiczne deklaracje metod a klasa abstrakcyjna może mieć również pola, zaimplementowane metody itp.

Ok, a więc po co nam interfejs skoro można to samo uzyskać za pomocą klasy abstrakcyjnej?

A no bo nie można dziedziczyć z wielu klas abstrakcyjnych a z interfejsów można

Dokładnie. Interfejsy istnieją w zamian za wielodziedziczenie.

Ale przecież wielodziedziczenie to zło! Przez to palą się wioski i roznosi się Ebola!

Tak tak, jasne jasne. A tak naprawdę to dlaczego ludzie mają taki problem z wielodziedziczeniem?

No bo zaciemnia strasznie kod. No i jeszcze ten słynny Deadly Diamond of Death!

Primo:
Jeżeli się nieumiejętnie korzysta z narzędzia to zawsze będzie ono zaciemniać kod – nieważne czy to wielodziedziczenie czy refleksja (na to jakoś nikt nie narzeka) czy wskaźniki czy nawet pojedyncze dziedziczenie.

Secundo:
Słynny DDD 1 jakoś nie przeszkodził ludziom od C++ zaimplementować wielodziedziczenia, ani ludziom od Pythona ani ludziom od Ruby’iego…
Wystarczyło napisać sprytny mechanizm obsługi takiego przypadku.

No ale skoro ani Java nie ma wielodziedziczenia ani C# to chyba z jakiegoś powodu?

C# nie ma bo Java nie miała, a Java… Nie chcę nawet wnikać.

Przedstawię za to problem na jaki my natknęliśmy się (po raz kolejny) w pracy.

Use case

Mamy do napisania prosty system:

  1. Zaloguj się do konta pocztowego
  2. Pobierz maile wychodzące
  3. Znajdź adresatów do których ów maile były wysłane
  4. Zrób z ów adresatami cokolwiek co ma sens biznesowy
  5. Zapisz wynik całego przeszukiwania

Zaprojektowany został mały “framework”:

Workflow
Framework

Workflow definuje proces działania dowolnej aplikacji – weź dane zadanie i przekaż je komuś kto wie jak je zrobić.
Dalej mamy MultiWorkflow – zadanie może mieć podzadania tak samo jak pracownicy mogą mieć podwładnych którym swoje podzadania przekazują.

Dzięki temu już na bazie układu klas możemy zobaczyć oryginalny zamysł:

  1. EmailboxWorker loguje się do skrzynki mailowej i pobiera emaile wychodzące
  2. Każda wiadomość trafia do EmailMessageWorker który wyciąga z niej adresatów
  3. Każdy adresat trafia do EmailRecipientWorker który robi z tym magiczne rzeczy

Po wykonaniu wszystkiego proces wraca przez wszystkie warstwy z powrotem do EmailboxWorker który zapisuje cały wynik do odpowiedniego miejsca.

Wszystko wygląda pięknie – tylko w C# jest to niemożliwe do zaimplementowania.

Zarówno ParentEmailJob jak i ParentEmailWorker muszą dziedziczyć z dwóch klas.
Powinny udostępniać zarówno funkcjonalność podzadań/podwładnych jak i posiadać kontekst emailowy.

  • ParentEmailJob : ParentJob, EmailJobInfo
  • ParentEmailWorker : ParentWorker, EmailWorker

A więc co teraz? Co robić, jak żyć?

Ehhh…

Nie jest to może nie do zaimplementowania, po prostu część funkcjonalności należy wstawić do środka klasy która potrzebuje wielodziedziczenia.
Np. przenieść kontekst emailowy bezpośrednio do ParentEmailJob i ParentEmailWorker…
No i nie jest to zbyt wygodne…

  1. nie, nie chodzi o domain driven design/development

Be First to Comment

A penny for your thoughts