Przejdź do zawartości

Zasada podstawienia Liskov

Z Wikipedii, wolnej encyklopedii

Zasada podstawienia Liskov (ang. Liskov substitution principle) – zasada projektowania w programowaniu obiektowym mówiąca, że:

Funkcje które używają wskaźników lub referencji do klas bazowych, muszą być w stanie używać również obiektów klas dziedziczących po klasach bazowych, bez dokładnej znajomości tych obiektów[1].

Inaczej mówiąc, klasa dziedzicząca powinna tylko rozszerzać możliwości klasy bazowej i w pewnym sensie nie zmieniać tego, co ona robiła już wcześniej. Mówiąc jeszcze inaczej – jeśli będziemy tworzyć egzemplarz klasy potomnej, to niezależnie od tego, co znajdzie się we wskaźniku na zmienną, wywoływanie metody, którą pierwotnie zdefiniowano w klasie bazowej, powinno dać kompatybilne rezultaty.

Zasada ta jest jednym z principiów SOLID i oznacza że klasa dziecka (dziedzicząca) powinna w logiczny i kompatybilny sposób implementować wszystkie metody klasy/interfaceu rodzica (błędem jest np. implementacja tylko części metod interfaceu i rzucanie wyjątków w pozostałych metodach).

Zasada została sformułowana po raz pierwszy przez Barbarę Liskov w książce Data Abstraction and Hierarchy[2], a spopularyzowana i podana w obecnym brzmieniu przez Roberta C. Martina w artykule "Principles of Object Oriented Design"[3] oraz książce "Agile Software Development: Principles, Patterns, and Practices"


Przykład naruszenia zasady

[edytuj | edytuj kod]

Załóżmy że mamy klasę abstrakcyjną (lub interface) MediaAbstract zawierającą m.in. metody getTitle() oraz toPdf(). Załóżmy że chcemy dziedziczyć tę klasę w klasach Article oraz Movie. Klasa Article może prawidłowo zaimplementować te metody, ale klasa Movie nie "ma jak" wygenerować pdf więc w implementacji metody toPdf() rzuca wyjątek "Z filmu nie da się wygenerować pdf".

Jest to naruszenie zasady podstawienia Liskov, ponieważ inny zewnętrzny kod który operuje na obiektach MediaAbstract, w trakcie normalnej pracy na nich nagle zatrzyma się i wyrzuci wyjątek gdy natrafi na obiekt typu Movie. Naprawa może powodować konieczność obsłużenia wyjątku w zewnętrznym kodzie, a więc wymusi jego zmianę (od czego ma chronić właśnie stosowanie zasady Liskov).

Jednym z możliwych rozwiązań tego problemu jest np. wydzielenie z klasy/interfaceu MediaAbstract metody toPdf() do osobnej klasy/interfaceu PdfExportable. Wówczas klasa Article będzie implementowała MediaAbstract oraz PdfExportable, natomiast klasa Movie tylko MediaAbstract.

Przykład drugi, naruszenia zasady

[edytuj | edytuj kod]

Załóżmy, że dana jest hierarchia dziedziczenia jak na diagramie poniżej.

Typowym przykładem naruszenia zasady LSP byłoby zastosowanie C++ Run-Time Type Information (RTTI) w celu rozpoznania typu obiektu, a następnie wywołanie funkcji, która wykonuje operację na danym obiekcie:

void przetwarzajFigurę(Figura& iFigura)
{
  if(typeid(iFigura) == typeid(Prostokąt))
    przetwarzajProstokąt(static_cast<Prostokąt&>(iFigura));

  else if(typeid(iFigura) == typeid(Okrąg))
    przetwarzajOkrąg(static_cast<Okrąg&>(iFigura));
  
  else if(typeid(iFigura) == typeid(Kwadrat))
    przetwarzajKwadrat(static_cast<Kwadrat&>(iFigura));
}

Funkcja przetwarzajFigurę() wprowadza dodatkowe zależności w kodzie, ponieważ musi ona znać wszystkie klasy dziedziczące po klasie Figura. Oznacza to, że każde utworzenie nowej klasy dziedziczącej po klasie Figura, będzie prawdopodobnie wiązało się ze zmianą tej funkcji.

Jeśli spojrzymy na klasy Prostokąt oraz Kwadrat, można zauważyć, że klasa Kwadrat dziedziczy po klasie Prostokąt metody do ustawiania/pobierania wysokości oraz szerokości. W przypadku klasy Prostokąt obecność tych metod jest naturalna (każdy prostokąt może mieć wysokość różną od szerokości). Nie jest tak w przypadku klasy kwadrat, ponieważ zmiana jego szerokości wiąże się ze zmianą wysokości. Oznacza to, że wywołanie metody ustawSzerokość na klasie Kwadrat spowodowałoby zmianę wartości zwracanej przez metodę pobierzWysokosc. W takim przypadku oprogramowanie musiałoby sprawdzać typ obiektu który zmienia, żeby wiedzieć w jaki sposób obiekt będzie się zachowywał po wprowadzeniu zmian. Taki styl programowania wprowadza dodatkowe zależności w kodzie i utrudnia jego późniejsze utrzymanie i rozwijanie.

Zobacz też

[edytuj | edytuj kod]

Przypisy

[edytuj | edytuj kod]
  1. Robert C. Martin: The Liskov Substitution Principle. [dostęp 2010-02-20]. [zarchiwizowane z tego adresu (2015-11-28)]. (ang.).
  2. Barbara Liskov: Data Abstraction and Hierarchy. SIGPLAN Notices, 1988.
  3. Robert C. Martin: The Principles of Object Oriented Design. [dostęp 2010-02-20]. (ang.).

Linki zewnętrzne

[edytuj | edytuj kod]