Jak zweryfikować zasadę podstawienia Liskowa w hierarchii dziedziczenia?

14

Zainspirowany odpowiedzią:

Wymaga tego zasada substytucji Liskowa

  • W podtypie nie można wzmocnić warunków wstępnych.
  • Warunki podrzędne nie mogą zostać osłabione w podtypie.
  • Niezmienniki nadtypu muszą być zachowane w podtypie.
  • Ograniczenie historii („reguła historii”). Obiekty są uważane za modyfikowalne tylko za pomocą ich metod (enkapsulacji). Ponieważ podtypy mogą wprowadzać metody, które nie są obecne w nadtypu, wprowadzenie tych metod może pozwolić na zmiany stanu w podtypie, które nie są dopuszczalne w nadtypie. Ograniczenia historyczne tego zabraniają.

Miałem nadzieję, że ktoś opublikuje hierarchię klas, która narusza te 4 punkty i jak je odpowiednio rozwiązać.
Szukam szczegółowego wyjaśnienia do celów edukacyjnych, w jaki sposób zidentyfikować każdy z 4 punktów w hierarchii i jak najlepiej to naprawić.

Uwaga:
miałem nadzieję opublikować próbkę kodu dla osób, nad którymi będą pracować, ale samo pytanie dotyczy tego, jak zidentyfikować wadliwe hierarchie :)

Songo
źródło
Istnieje kilka innych przykładów naruszeń LSP w odpowiedziach na to SO pytanie
StuartLC

Odpowiedzi:

17

Jest to o wiele prostsze niż ten cytat sprawia, że ​​brzmi on trafnie i dokładnie.

Kiedy spojrzysz na hierarchię dziedziczenia, wyobraź sobie metodę, która odbiera obiekt klasy podstawowej. Teraz zadaj sobie pytanie, czy są jakieś założenia, które mógłby dokonać ktoś edytujący tę metodę, które byłyby nieprawidłowe dla tej klasy.

Na przykład ( pierwotnie widoczny na stronie wuja Boba ):

public class Square : Rectangle
{
    public Square(double width) : base(width, width)
    {
    }

    public override double Width
    {
        set
        {
            base.Width = value;
            base.Height = value;
        }
        get
        {
            return base.Width;
        }
    }

    public override double Height
    {
        set
        {
            base.Width = value;
            base.Height = value;
        }
        get
        {
            return base.Height;
        }
    }
}

Wydaje się dość sprawiedliwe, prawda? Stworzyłem specjalistyczny rodzaj prostokąta o nazwie Kwadrat, który utrzymuje, że szerokość musi zawsze być równa wysokości. Kwadrat jest prostokątem, więc pasuje do zasad OO, prawda?

Ale poczekaj, co jeśli ktoś teraz napisze tę metodę:

public void Enlarge(Rectangle rect, double factor)
{
    rect.Width *= factor;
    rect.Height *= factor;
}

Nie fajnie. Ale nie ma powodu, dla którego autor tej metody powinien był wiedzieć, że może istnieć potencjalny problem.

Za każdym razem, gdy wywodzisz jedną klasę od drugiej, pomyśl o klasie podstawowej i co ludzie mogą o niej sądzić (np. „Ma szerokość i wysokość i oboje byliby niezależni”). Pomyśl więc: „czy te założenia pozostają aktualne w mojej podklasie?” Jeśli nie, przemyśl swój projekt.

pdr
źródło
Bardzo dobry i subtelny przykład. +1. Co możesz zrobić, to uczynić z metody powiększania klasę Rectangle i zastąpić ją w klasie Square.
marco-fiset
@ marco-fiset: Wolałbym, żeby Kwadrat i Prostokąt były oddzielone, Kwadrat z tylko jednym wymiarem, ale każdy z nich ma IResizable. Prawdą jest, że gdyby istniała metoda Draw, byłyby one podobne, ale wolałbym, aby obie zawierały klasę RectangleDrawer, która zawiera wspólny kod.
pdr
1
Nie sądzę, że to dobry przykład. Problem polega na tym, że kwadrat nie ma szerokości ani wysokości. Ma tylko długość boków. Problem nie pojawiłby się, gdyby szerokość i wysokość były czytelne, ale w tym przypadku można je zapisać. Wprowadzając stan modyfikowalny, zawsze jest o wiele trudniej utrzymać LSP.
SpaceTrucker
@pdr Dzięki za przykład, ale jeśli chodzi o 4 warunki, o których wspomniałem w moim poście, która część Squareklasy je narusza?
Songo,
1
@Songo: To ograniczenie historii. Lepiej wyjaśniono tutaj: blackwasp.co.uk/LSP.aspxPodklasy ze swej natury obejmują wszystkie metody i właściwości ich nadklas. Mogą także dodawać kolejnych członków. Ograniczenie historii mówi, że nowi lub zmodyfikowani członkowie nie powinni modyfikować stan obiektu w sposób, który nie byłby dozwolony przez klasę podstawową . Na przykład, jeśli klasa podstawowa reprezentuje obiekt o stałym rozmiarze, podklasa nie powinna zezwalać na modyfikację tego rozmiaru. ”
pdr