Podstawowe zasady projektowania obiektowego

Transkrypt

Podstawowe zasady projektowania obiektowego
Śliwioski Paweł, 30.04.2009
Podstawowe zasady projektowania
obiektowego
Wstęp
Programowanie zorientowane obiektowo dominuje w świecie wytwarzania oprogramowania
od ponad dwóch dekad, jednak wiele projektów obiektowych nadal nie jest do kooca zgodnych z jego
ideą. Wielu programistów wykorzystuje języki obiektowe takie jak Java czy C# do tworzenia kodu
proceduralnego, który jest trudny w konserwacji i podatny na błędy. W dalszej części przedstawię
zasady dzięki, którym będziemy mogli powiedzied, że nasz projekt jest w pełni obiektowy.
Zasada poj edyncze j odpowi edzi alności
Zasada pojedynczej odpowiedzialności
Klasa powinna mied wyłącznie jeden obszar odpowiedzialności.
Obszar odpowiedzialności klasy jest to zakres - zbliżonych funkcjonalnie – działao, które może
wykonywad obiekt będący jej instancją. Przykładowo na poniższym rysunku klasa Kolo posiada dwa
obszary odpowiedzialności – rysowanie oraz wykonywanie obliczeo matematycznych.
Z klasy tej korzysta Aplikacja graficzna oraz Moduł matematyczny. Taka sytuacja powoduje poważny
problem. Jeśli Aplikacja graficzna wymusi zmianę klasy Kolo, musimy uwzględnid tą zmianę w module
matematycznym. W najlepszym przypadku będziemy musieli ponownie go skompilowad i
przetestowad, w najgorszym zmuszeni będziemy do modyfikacji jego kodu. W celu rozwiązania tego
problemu, należy podzielid obszary odpowiedzialności między dwie odrębne klasy. Przykładowe
rozwiązanie prezentuje poniższy diagram.
Przypisanie obszarów odpowiedzialności do odrębnych klas sprawiło, że zmiany w klasie Kolo powodowane zmianą wymagao Aplikacji graficznej - nie mają żadnego wpływu na funkcjonowanie
Modułu matematycznego.
1
Zasada otwarte-zamknięta
Zasada otwarte-zamknięte
Składniki oprogramowania (klasy, moduły, funkcje itp.) powinny byd otwarte na rozbudowę, ale
zamknięte dla modyfikacji 1 .
Składnik oprogramowania zgodny z tą zasadą, powinien udostępniad mechanizm swojej
rozbudowy, nie ingerujący w jego istniejący kod, a więc w celu jego rozbudowy, dodajemy
dodatkowe struktury obiektowe (tj. klasy, interfejsy, łączące je relacje), a nie modyfikujemy struktur
już istniejących. Zgodnośd z tą zasadą można osiągnąd stosując odpowiednie wzorce projektowe tj.
Strategy lub Template Method. Przykład klasy Importer, niezgodnej z zasadą otwarte-zamknięte,
przedstawiam na poniższym diagramie.
Gdybyśmy chcieli rozszerzyd ten model o możliwośd importu danych typu ImportYData, musielibyśmy
obsłużyd go w klasie Importer, co jest naruszeniem zasady otwarte-zamknięte. Sposób eliminacji tego
problemu, z wykorzystaniem wzorca Strategy, prezentuje poniższy diagram klas:
W celu rozbudowy tej struktury o dodatkowe typy importowanych danych, dodajemy kolejne klasy
implementujące interfejs ImportInterface, natomiast nie musimy wprowadzad żadnych zmian do
istniejącego kodu. Zachowanie zgodności z zasadą otwarte-zamknięte jest dośd kosztowne.
Tworzenie niezbędnych abstrakcji wymaga dodatkowej pracy programisty, a same klasy zwiększają
złożonośd projektu, dlatego zaleca się stosowanie jej dla tych fragmentów systemu, które są
najbardziej podatne na zmiany.
Zasada podstawiania Li skov
Zasada podstawiania Liskov
Musi istnied możliwośd zastępowania typów bazowych ich podtypami 2.
Naruszenie tej zasady stwarza jeden podstawowy problem. W każdym miejscu programu, w
którym oczekujemy typu B, będącego klasą bazową typu P, użycie typu P spowoduje nieprawidłowe
1
2
Robert C. Martin i Micah Martin, Agile Principles, Pa tterns, and Pra cti ces In C#, Prentice Hall, 2007, s. 122.
Robert C. Martin i Micah Martin, Agile Principles, Pa tterns, and Pra cti ces In C#, Prentice Hall, 2007, s. 136.
2
działanie programu. Co gorsza, w celu eliminacji tego problemu, często stosuje się konstrukcje
postaci:
void F(B b)
{
if(b is P)
(b as P).Wykonaj();
}
Powoduje ona degradację kodu i w oczywisty sposób narusza zasadę Otwarte-zamknięte (stworzenie
nowego typu potomnego klasy B, spowoduje koniecznośd uzupełnienia funkcji F o dodatkowy
warunek). W celu eliminacji tego rodzaju problemów, należy w klasie bazowej stosowad odpowiednią
metodę wirtualną i nadpisywad ją w klasach potomnych. Podejście takie umożliwia podstawienie
typu P w miejsce B, co eliminuje koniecznośd sprawdzania rzeczywistego typu obiektu, który został
przekazany funkcji.
Zasada odwracana zal eżności
Zasada odwracania zależności
Moduły wysokiego poziomu nie powinny zależed od modułów niskopoziomowych. Obie grupy
modułów powinny zależed od abstrakcji.3
W większości aplikacji to moduły wysokiego poziomu opisują strategie działania sytemu.
Dlatego zgodnie z logiką moduły te nie powinny zależed od szczegółowych mechanizmów warstw
niskopoziomowych. Przeanalizujmy poniższy przykład 4:
Klasa Przycisk reprezentuje wysokopoziomową strategię aktywacji / dezaktywacji przycisku w
zależności od czynników zewnętrznych. Natomiast klasa Czajnik zawiera niskopoziomowe
mechanizmy obsługi działania czajnika elektrycznego. Podejście zgodne z powyższym diagramem
sprawia, że zmiany w klasie Czajnik wymuszą modyfikację klasy Przycisk, dodatkowo w takim modelu
obiekty klasy Przycisk mogę sterowad wyłącznie obiektami klasy Czajnik. W celu eliminacji tych
problemów należy odwrócid zależnośd, tak jak przedstawiono to na poniższym diagramie:
3
4
Robert C. Martin i Micah Martin, Agile Principles, Pa tterns, and Pra cti ces In C#, Prentice Hall, 2007, s. 154.
Przykład zaczerpnięty z książki Agile Principles, Patterns, and Pra cti ces In C#, Pr entice Hall, 2007
3
W tej postaci obiekty klasy Przycisk zależą wyłącznie od abstrakcji (SwitchableDevice) i mogą
sterowad dowolnym obiektem klasy implementującej interfejs SwitchableDevice np. obiektem klasy
Telewizor.
4

Podobne dokumenty