Załóżmy, że mam interfejs FooInterface
z następującą sygnaturą:
interface FooInterface {
public function doSomething(SomethingInterface something);
}
I konkretna klasa, ConcreteFoo
która implementuje ten interfejs:
class ConcreteFoo implements FooInterface {
public function doSomething(SomethingInterface something) {
}
}
Chciałbym ConcreteFoo::doSomething()
zrobić coś wyjątkowego, jeśli przejdzie on przez specjalny typ SomethingInterface
obiektu (powiedzmy, że się nazywa SpecialSomething
).
Zdecydowanie jest to naruszenie LSP, jeśli wzmocnię warunki wstępne metody lub wyrzucę nowy wyjątek, ale czy nadal byłoby to naruszenie LSP, jeśli stworzę SpecialSomething
obiekty specjalne , zapewniając rezerwę dla SomethingInterface
obiektów ogólnych ? Coś jak:
class ConcreteFoo implements FooInterface {
public function doSomething(SomethingInterface something) {
if (something instanceof SpecialSomething) {
// Do SpecialSomething magic
}
else {
// Do generic SomethingInterface magic
}
}
}
doSomething()
metody byłaby konwersja typu naSpecialSomething
: jeśli ją otrzymaSpecialSomething
, po prostu zwróci obiekt niezmodyfikowany, natomiast jeśli otrzymaSomethingInterface
obiekt ogólny , uruchomi algorytm, aby przekonwertować go naSpecialSomething
obiekt. Ponieważ warunki wstępne i dodatkowe pozostają takie same, nie sądzę, aby umowa została naruszona.y = doSomething(x)
, a potemx.setFoo(3)
, a potem okaże się, żey.getFoo()
zwraca 3?SpecialSomething
zamiast tego kopię obiektu. Chociaż ze względu na czystość, mogłem również zrezygnować z optymalizacji przypadków specjalnych, gdy obiekt przechodzi,SpecialSomething
i po prostu uruchomić go przez większy algorytm konwersji, ponieważ nadal powinien działać, ponieważ jest on równieżSomethingInterface
obiektem.To nie jest naruszenie LSP. Nadal jednak narusza zasadę „nie rób sprawdzeń typów”. Lepiej byłoby zaprojektować kod w taki sposób, aby specjalny kod pojawiał się naturalnie; może
SomethingInterface
potrzebuje innego członka, który mógłby to osiągnąć, a może trzeba gdzieś wstrzyknąć abstrakcyjną fabrykę.Jednak nie jest to trudna i szybka reguła, więc musisz zdecydować, czy kompromis jest tego wart. W tej chwili masz zapach kodu i potencjalną przeszkodę dla przyszłych ulepszeń. Pozbycie się tego może oznaczać znacznie bardziej skomplikowaną architekturę. Bez dodatkowych informacji nie mogę powiedzieć, który z nich jest lepszy.
źródło
Nie, wykorzystanie faktu, że dany argument nie tylko zapewnia interfejs A, ale także A2, nie narusza LSP.
Tylko upewnij się, że specjalna ścieżka nie ma żadnych silniejszych warunków wstępnych (oprócz tych przetestowanych przy podejmowaniu decyzji o jej przyjęciu), ani żadnych słabszych warunków wstępnych.
Szablony C ++ często to robią, aby zapewnić lepszą wydajność, na przykład wymagając
InputIterator
s, ale dając dodatkowe gwarancje, jeśli są wywoływane zRandomAccessIterator
s.Jeśli zamiast tego musisz zdecydować w czasie wykonywania (na przykład za pomocą dynamicznego rzutowania), strzeż się decyzji, którą ścieżkę wybrać, wykorzystując wszystkie potencjalne zyski lub nawet więcej.
Skorzystanie ze specjalnego przypadku często jest sprzeczne z OSUSZANIEM (nie powtarzaj się), ponieważ może być konieczne zduplikowanie kodu, oraz z KISS (Keep it Simple), ponieważ jest bardziej złożony.
źródło
Istnieje kompromis między zasadą „nie rób sprawdzeń typów” i „segreguj swoje interfejsy”. Jeśli wiele klas zapewnia wykonalne, ale być może nieefektywne środki do wykonywania niektórych zadań, a kilka z nich może również oferować lepsze środki, i istnieje zapotrzebowanie na kod, który może zaakceptować dowolną szerszą kategorię elementów, które mogą wykonać zadanie (być może nieefektywnie), ale następnie wykonać zadanie tak efektywnie, jak to możliwe, konieczne będzie, aby wszystkie obiekty zaimplementowały interfejs zawierający element członkowski, aby powiedzieć, czy obsługiwana jest bardziej wydajna metoda, i inny, aby użyć go, jeśli jest, lub kod, który odbiera obiekt, sprawdza, czy obsługuje rozszerzony interfejs i rzutuje go, jeśli tak jest.
Osobiście popieram poprzednie podejście, choć chciałbym, aby frameworki zorientowane obiektowo, takie jak .NET, pozwoliłyby interfejsom na określenie domyślnych metod (dzięki czemu większe interfejsy byłyby mniej bolesne w pracy). Jeśli wspólny interfejs zawiera opcjonalne metody, wówczas pojedyncza klasa opakowania może obsługiwać obiekty o wielu różnych kombinacjach zdolności, jednocześnie obiecując konsumentom tylko te umiejętności, które występują w oryginalnym zawiniętym obiekcie. Jeśli wiele funkcji jest podzielonych na różne interfejsy, wówczas dla każdej innej kombinacji interfejsów, które mogą wymagać obsługi zawijanych obiektów, potrzebny będzie inny obiekt opakowania.
źródło
Zasada podstawienia Liskowa dotyczy podtypów działających zgodnie z umową ich nadtypu. Tak, jak napisał Ixrec, nie ma wystarczających informacji, aby odpowiedzieć, czy jest to naruszenie LSP.
Naruszono tu jednak zasadę otwartego zamkniętego. Jeśli masz nowy wymóg - wykonaj magię SpecialSomething - i musisz zmodyfikować istniejący kod, to zdecydowanie naruszasz OCP .
źródło