Powiedzmy, że mamy listę encji Zadania i ProjectTask
podtyp. Zadania można zamknąć w dowolnym momencie, z wyjątkiem tych, ProjectTasks
których nie można zamknąć, gdy mają status Uruchomione. Interfejs użytkownika powinien upewnić się, że opcja zamknięcia uruchomionego ProjectTask
nigdy nie jest dostępna, ale w domenie istnieją pewne zabezpieczenia:
public class Task
{
public Status Status { get; set; }
public virtual void Close()
{
Status = Status.Closed;
}
}
public class ProjectTask : Task
{
public override void Close()
{
if (Status == Status.Started)
throw new Exception("Cannot close a started Project Task");
base.Close();
}
}
Teraz podczas wywoływania Close()
Zadania istnieje szansa, że połączenie zakończy się niepowodzeniem, jeśli ma ProjectTask
status Rozpoczęty, a nie będzie, jeśli będzie to zadanie podstawowe. Ale to są wymagania biznesowe. To powinno zawieść. Czy można to uznać za naruszenie zasady substytucji Liskowa ?
design
object-oriented-design
solid
liskov-substitution
Paul T. Davies
źródło
źródło
public Status Status { get; private set; }
:; w przeciwnym razieClose()
można obejść tę metodę.Task
nie wprowadzają dziwnych niezgodności w kodzie polimorficznym, o których tylko wie się,Task
to wielka sprawa. LSP nie jest kaprysem, ale został wprowadzony właśnie w celu ułatwienia konserwacji w dużych systemach.TaskCloser
proces, któryclosesAllTasks(tasks)
. Ten proces oczywiście nie próbuje wychwycić wyjątków; w końcu nie jest to część wyraźnej umowy zTask.Close()
. Teraz wprowadzaszProjectTask
i nagle zaczynaszTaskCloser
rzucać (prawdopodobnie nieobsługiwane) wyjątki. To wielka sprawa!Odpowiedzi:
Tak, jest to naruszenie LSP. Wymaga tego zasada Liskowa
Twój przykład łamie pierwszy wymóg, wzmacniając warunek wstępny wywołania
Close()
metody.Możesz to naprawić, przenosząc wzmocniony warunek wstępny na najwyższy poziom hierarchii dziedziczenia:
Zastrzegając, że wywołanie
Close()
jest ważne tylko w stanie, gdyCanClose()
zwrotytrue
, warunek wstępny stosuje się zarówno do,Task
jak i doProjectTask
, naprawiając naruszenie LSP:źródło
Close
sprawdzanie na najwyższym poziomie i dodawanie chronionejDoClose
byłoby prawidłową alternatywą. Chciałem jednak pozostać jak najbliżej przykładu PO; poprawić to osobne pytanie.Tak. Narusza to LSP.
Moją sugestią jest dodanie
CanClose
metody / właściwości do zadania podstawowego, aby każde zadanie mogło stwierdzić, czy zadanie w tym stanie można zamknąć. Może również podać powód. I usuń wirtualny zClose
.Na podstawie mojego komentarza:
źródło
Zasada podstawienia Liskowa stanowi, że klasa podstawowa powinna być wymienna na dowolną z jego podklas bez zmiany jakichkolwiek pożądanych właściwości programu. Ponieważ tylko
ProjectTask
podnosi wyjątek po zamknięciu, program musiałby zostać zmieniony, aby pomieścił to, należyProjectTask
go zastąpićTask
. To jest naruszenie.Ale jeśli zmodyfikujesz
Task
stwierdzając w swoim podpisie, że może powodować wyjątek po zamknięciu, nie naruszysz zasady.źródło
Naruszenie LSP wymaga trzech stron. Typ T, podtyp S i program P, który używa T, ale otrzymuje instancję S.
Twoje pytanie zawiera T (Zadanie) i S (ProjectTask), ale nie P. Więc twoje pytanie jest niekompletne, a odpowiedź jest kwalifikowana: Jeśli istnieje P, które nie oczekuje wyjątku, to dla tego P masz LSP naruszenie. Jeśli każde P oczekuje wyjątku, oznacza to, że nie ma naruszenia LSP.
Jednak zrobić mają SRP naruszenie. Fakt, że stan zadania można zmienić, oraz polityka , zgodnie z którą niektóre zadania w niektórych stanach nie powinny być zmieniane na inne, to dwie bardzo różne obowiązki.
Te dwie obowiązki zmieniają się z różnych powodów i dlatego należy je rozdzielić. Zadania powinny obsłużyć fakt bycia zadaniem oraz dane powiązane z zadaniem. TaskStatePolicy powinien obsłużyć sposób przejścia zadań ze stanu do stanu w danej aplikacji.
źródło
OpenTaskException
(podpowiedzi, podpowiedzi) i każde P oczekuje wyjątku, to co to znaczy o kodzie interfejsu, a nie implementacji? O czym mówię Nie wiem Jestem po prostu zaskoczony, że komentuję odpowiedź Unca 'Bob.Może to, ale nie musi, stanowić naruszenie LSP.
Poważnie. Wysłuchaj mnie.
Jeśli podążasz za LSP, obiekty typu
ProjectTask
muszą zachowywać się tak, jakTask
oczekuje się od obiektów typu .Problem z twoim kodem polega na tym, że nie udokumentowałeś, jak
Task
powinny zachowywać się obiekty typu . Napisałeś kod, ale żadnych umów. Dodam umowę naTask.Close
. W zależności od dodanej przeze mnie umowy kod dlaProjectTask.Close
LSP jest zgodny z LSP lub nie.Biorąc pod uwagę następującą umowę dla Task.Close, kod dla
ProjectTask.Close
nie jest zgodny z LSP:Biorąc pod uwagę następującą umowę dla Task.Close, kod dla
ProjectTask.Close
jest zgodny z LSP:Metody, które można zastąpić, należy udokumentować na dwa sposoby:
„Zachowanie” dokumentuje to, na czym może polegać klient, który wie, że obiekt adresata jest
Task
, ale nie wie, do której klasy jest bezpośrednią instancją. Informuje także projektantów podklas, które przesłonięcia są uzasadnione, a które nie.„Zachowanie domyślne” dokumentuje to, na czym może polegać klient, który wie, że obiekt adresata jest bezpośrednim wystąpieniem
Task
(tj. Co otrzymujesz, jeśli go użyjesznew Task()
. Mówi także projektantom podklas, jakie zachowanie zostanie odziedziczone, jeśli tego nie zrobi przesłonić metodę.Teraz powinny obowiązywać następujące relacje:
źródło
Close
rzuca. Więc podpis deklaruje, że wyjątek może zostać zgłoszony - nie oznacza, że nie zostanie zgłoszony. Pod tym względem Java radzi sobie lepiej. Mimo to, jeśli zadeklarujesz, że metoda może zadeklarować wyjątek, powinieneś udokumentować okoliczności, w których może (lub będzie). Twierdzę więc, że aby mieć pewność, czy LSP zostanie naruszone, potrzebujemy dokumentacji wykraczającej poza podpis.if (false) throw new Exception("cannot start")
do klasy podstawowej. Kompilator go usunie, a mimo to kod zawiera potrzebne informacje. Btw. nadal mamy naruszenie LSP przy tych obejściach, ponieważ warunek jest nadal wzmocniony ...Nie stanowi to naruszenia zasady substytucji Liskowa.
Zasada substytucji Liskowa mówi:
Powód, dla którego wdrożenie tego podtypu nie jest naruszeniem zasady substytucji Liskowa, jest dość prosty: nic nie można udowodnić na temat tego, co
Task::Close()
faktycznie robi. Jasne,ProjectTask::Close()
zgłasza wyjątek podczasStatus == Status.Started
, ale tak możeStatus = Status.Closed
sięTask::Close()
.źródło
Tak, to naruszenie.
Sugerowałbym, abyś miał swoją hierarchię do tyłu. Jeśli nie każdy
Task
można zamknąć, toclose()
nie należy doTask
. Być może potrzebujesz interfejsu,CloseableTask
którego wszyscy nieProjectTasks
mogą wdrożyć.źródło
Task
sam się nie implementujeCloseableTask
, robią niebezpieczne miejsce, by nawet zadzwonićClose()
.Oprócz tego, że jest to problem LSP, wygląda na to, że używa wyjątków do kontrolowania przepływu programu (muszę założyć, że złapiesz gdzieś ten trywialny wyjątek i wykonasz jakiś niestandardowy przepływ, zamiast pozwolić, aby zawiesił twoją aplikację).
Wydaje się, że jest to dobre miejsce do wdrożenia wzorca stanu dla TaskState i umożliwienia obiektom stanu zarządzania prawidłowymi przejściami.
źródło
Brakuje mi tutaj ważnej rzeczy związanej z LSP i projektowaniem na podstawie umowy - w warunkach wstępnych to osoba dzwoniąca jest odpowiedzialna za upewnienie się, że warunki wstępne są spełnione. Kod wywoływany w teorii DbC nie powinien weryfikować warunku wstępnego. Umowa powinna określać, kiedy zadanie może zostać zamknięte (np. CanClose zwraca wartość True), a następnie kod wywołujący powinien zapewnić spełnienie warunku wstępnego przed wywołaniem funkcji Close ().
źródło
ProjectTask
. Jest to warunek końcowy (mówi o tym, co dzieje się po wywołaniu metody), a spełnienie go jest obowiązkiem wywoływanego kodu.Tak, jest to wyraźne naruszenie LSP.
Niektórzy twierdzą tutaj, że wyraźne w klasie podstawowej, że podklasy mogą zgłaszać wyjątki, uczyniłoby to akceptowalnym, ale nie sądzę, że to prawda. Bez względu na to, co dokumentujesz w klasie podstawowej lub na jaki poziom abstrakcji przenosisz kod, warunki wstępne będą nadal wzmocnione w podklasie, ponieważ dodasz do niej część „Nie można zamknąć rozpoczętego zadania projektowego”. Nie jest to coś, co można rozwiązać za pomocą obejścia, potrzebujesz innego modelu, który nie narusza LSP (lub musimy poluzować ograniczenie „warunków wstępnych nie można wzmocnić”).
Możesz wypróbować wzór dekoratora, jeśli chcesz uniknąć naruszenia LSP w tym przypadku. To może zadziałać, nie wiem.
źródło