OOP: Kilka słów o abstrakcjach

Abstrakcja
Kazimierz Malewicz. Czerwony Dom

Do napisania tego postu skłoniły mnie dwie kwestie. Pierwszą był często oglądany przeze mnie kod z wszechobecnymi interfejsami, których jedynym sensem istnienia była rejestracja w kontenerze DI, lub mockowanie dla testów jednostkowych. Druga to dyskusja w której miałem okazję uczestniczyć a która odbyła się jakiś czas temu w mojej pracy. Przyczynkiem do niej było przeprowadzone przeze mnie code review, po którym padło pytanie: “Dlaczego uważasz, że interfejsy i klasy w stosunku 1:1  (jednej klasie odpowiada jeden interfejs) to code smell?“, a jako kontra przywołana została dobrze znana mantra: <<Program to interface not to an implementation>> oraz kwestia, że powinniśmy uzależniać kod od abstrakcji, a przecież interfejs to właśnie abstrakcja. Być może ten post powinien nosić tytuł “Kilka słów o interfejsach” – na taki też znajdzie się niebawem czas, nie mniej postanowiłem zacząć, jak Pan Bóg przykazał, od początku 🙂

Spójrzmy najpierw na kilka definicji abstrakcji jako takiej dla kilku popularnych dziedzin i spróbujmy, jakże by inaczej, wyabstrahować to co je łączy.

Definicja abstrakcji w filozofii(za Wikipedią)

Abstrakcja to proces tworzenia pojęć, w którym wychodząc od rzeczy jednostkowych (najczęściej konkretnych) dochodzi się do pojęcia bardziej ogólnego poprzez konstatowanie tego, co dla tych rzeczy wspólne (zazwyczaj właściwości).

Definicja abstrakcji w matematyce(za Wikipedią)

Abstrakcja to sposób rozumowania leżący u podstaw matematyki, polegający na odrzuceniu części cech przedmiotów fizycznych w celu wyeksponowania cech pożądanych. Wszystkie obiekty matematyczne powstały na tej drodze. Utworzone w ten sposób obiekty są obiektami idealnymi, a nie realnymi.

Definicja abstrakcji w psychologii(za Wikipedią)

Abstrakcja, abstrahowanie jest to jedna z operacji umysłowych, także proces uczenia się pojęć. Proces abstrahowania polega na pomijaniu różnic między egzemplarzami danego zbioru i wyodrębnianiu ich cech wspólnych

Każda z powyższych definicji mówi o szukaniu cech wspólnych w zestawie elementów danego rodzaju, po to by móc później zamknąć je w jednym zbiorze. Widząc Owczarka niemieckiego, Labradora i Pudla, nikt raczej nie będzie miał mi za złe jeżeli powiem że widzę trzy psy, chociaż te różnią się między sobą diametralnie.

A jak to się ma do abstrakcji w ujęciu programistycznym? Tu już jest ciekawiej.

Abstrakcja w programowaniu

Programując mamy szansę wykorzystać pojęcie abstrakcji na dwóch poziomach.

  • Pierwszy poziom –  korzystają z niego , jak mi się wydaje, wszyscy programiści. Dostając wymagania i przystępując do pisania kodu jesteśmy zwykle zmuszeni do uproszczenia złożonego problemu do postaci wystarczającej na jego rozwiązanie. Musząc obliczyć powierzchnię domu, potrzebujemy obwodu ścian wewnątrz, natomiast całkowicie nie interesuje nas kolor elewacji – ta informacja zwiększyłaby niepotrzebnie złożoność kodu a nie wniosła by nic po za iluzją prawdziwego domu. Tego typu uproszczenie możemy zamknąć w klasie, metodzie czy procedurze – niezależnie od tego nasz kod będzie stanowił abstrakcję problemu, którą możemy wykorzystać do liczenia powierzchni różnych budynków – nie musimy pisać nowej aplikacji dla nowego domu – i własnie dzięki temu niezależnie czy nasza abstrakcja jest gorsza czy lepsza, ale zawsze jest to abstrakcja.
  • Drugi poziom – będący głównym tematem dalszej części tego wpisu – polega na szukaniu abstrakcji w odniesieniu do implementacji. Tu już w ogóle zaczyna się roller coaster :). Na tym poziomie szukamy wspólnych elementów klas (ale także metod, czy przestrzeni nazw, lub nawet bibliotek), które możemy zamknąć np. w klasie bazowej co pozwoli nam zignorować zbędne szczegóły – zmniejszamy tym złożoność przez co kod jest łatwiejszy zarówno do zrozumienia. Zmniejszamy też “sztywność” kodu dzięki czemu kod jest łatwiejszy do utrzymania. Korzystanie z abstrakcji na tym poziomie wymaga znajomości dobrych praktyk, umiejętności, wyczucia a może nawet szczypty szczęścia ;).

Zasadniczo można się chyba pokusić o stwierdzenie, że programowanie obiektowe jest niczym innym jak sztuką znajdowania dobrych abstrakcji. W dalszej części będę pisał o abstrakcjach, które zaliczyłem do drugiego poziomu.

Co sprawia, że daną klasę/interfejs/metodę możemy nazywać abstrakcją?

W mojej opinii jest kilka warunków koniecznych by dany element, stanowiący implementację opartą na określonym konstrukcie języka programowania, nazywać abstrakcją (ostatni już raz przypomnę, że chodzi mi o abstrakcję drugiego poziomu) :

  1. Istnienie co najmniej dwóch elementów, które uogólnia. Interfejs, czy klasa bazowa nie są abstrakcją jeżeli “uogólniamy” nimi jedną klasę. Jeżeli na świecie jedyną rasą psów byłby Pudel, to jedno z tych określeń nie miałoby racji bytu, chyba że jako zamiennik :).
  2. W dowolnej wielkości zbiorze uogólnianych elementów co najmniej dwa (aczkolwiek im więcej tym lepiej) nie są powodowane wymogami używanej technologii. Czyli jeżeli mój interfejs jest implementowany przez dwie klasy, z których jedna jest mockiem na cele testów jednostkowych to nie nazywałbym owego interfejsu abstrakcją.
  3. Dla dziedziczenia spełniona jest Zasada Podstawiania Liskov.

Co sprawia, że nasza abstrakcja jest dobra?

Jeżeli wskazane wyżej warunki są spełnione to – w mojej opinii mamy do czynienia z abstrakcją . Teraz pora na kilka elementów, które pozwalają na zwiększenie jej jakości.

  1. Nasza abstrakcja nie jest uzależniona od implementacji a od innych abstrakcji (chyba, że takowych więcej nie ma ;))
  2. Nasza abstrakcja nie ujawnia szczegółów implementacji (nie jestem zwolennikiem eksponowania w interfejsach właściwości, co w C# jest dosyć powszechne)
  3. Implementacje abstrakcji nie łamią Prawa Demeter

Tak w mojej opinii wygląda kwestia abstrakcji w programowaniu. Zapraszam do polemiki 🙂

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *