To zdjęcie pochodzi z aplikacji Projektowanie i wzorce oparte na domenie: z przykładami w języku C # i .NET
To jest diagram klasowy dla Wzorca Stanu, w którym a SalesOrder
może mieć różne stany w czasie swojego życia. Tylko niektóre przejścia są dozwolone między różnymi stanami.
Teraz OrderState
klasa jest abstract
klasą, a wszystkie jej metody są dziedziczone w jej podklasach. Jeśli weźmiemy podklasę Cancelled
za stan końcowy, który nie zezwala na żadne przejścia do innych stanów, będziemy musieli zastosować override
wszystkie jej metody w tej klasie, aby zgłaszać wyjątki.
Czy nie narusza to zasady substytucji Liskowa, ponieważ podklasa nie powinna zmieniać zachowania rodzica? Czy zmiana klasy abstrakcyjnej na interfejs to rozwiązuje?
Jak to może zostać naprawione?
cancel()
zmienia stan na anulowany.Odpowiedzi:
Ta konkretna implementacja, tak. Jeśli uczynisz stany konkretnymi klasami, a nie abstrakcyjnymi implementatorami, to odejdziesz od tego.
Jednak wzorzec stanu, do którego się odwołujesz, jest w rzeczywistości projektem maszyny stanów, ogólnie rzecz biorąc, nie zgadzam się z tym, jak widzę, jak rośnie. Sądzę, że istnieją wystarczające podstawy, aby potępić to jako naruszenie zasady pojedynczej odpowiedzialności, ponieważ te wzorce zarządzania stanem stają się centralnymi repozytoriami wiedzy o obecnym stanie wielu innych części systemu. Ten element zarządzania stanem, który jest scentralizowany, będzie wymagał reguł biznesowych istotnych dla wielu różnych części systemu w celu ich racjonalnej koordynacji.
Wyobraź sobie, że wszystkie części systemu, które dbają o stan, znajdują się w różnych usługach, różnych procesach na różnych maszynach, centralny menedżer statusu, który szczegółowo określa status każdego z tych miejsc, skutecznie wąskie gardła całego tego rozproszonego systemu i myślę, że wąskie gardło jest znakiem naruszenia SRP, a także ogólnie złego projektu.
W przeciwieństwie do tego sugerowałbym, aby uczynić obiekty bardziej inteligentnymi, ponieważ w obiekcie Model we wzorze MVC, w którym model wie, jak sobie radzić, nie potrzebuje zewnętrznego orkiestratora, aby zarządzać swoimi wewnętrznymi działaniami lub uzasadnieniem.
Nawet umieszczając taki wzór stanu wewnątrz obiektu, aby zarządzał tylko sobą, wydaje się, że zrobiłbyś ten obiekt zbyt dużym. Powiedziałbym, że przepływy pracy powinny odbywać się poprzez komponowanie różnych samowystarczalnych obiektów, a nie za pomocą jednego uporządkowanego stanu, który zarządza przepływem innych obiektów lub przepływem inteligencji w sobie.
Ale w tym momencie jest to więcej sztuki niż inżynierii, a zatem zdecydowanie subiektywne jest twoje podejście do niektórych z tych rzeczy, które mówi, że zasady są dobrym przewodnikiem i tak, implementacja, którą wymieniasz, jest naruszeniem LSP, ale może być poprawiona, aby nie była. Zachowaj ostrożność podczas korzystania z SRP podczas korzystania z tego rodzaju wzorów i prawdopodobnie będziesz bezpieczny.
źródło
Jest to powszechna błędna interpretacja LSP. Podklasa może zmieniać zachowanie elementu nadrzędnego, o ile pozostaje ono zgodne z typem elementu nadrzędnego.
W Wikipedii istnieje dobre, długie wyjaśnienie , sugerujące rzeczy, które naruszą LSP:
Osobiście łatwiej mi to zapamiętać: jeśli patrzę na parametr w metodzie typu A, czy ktoś przekazujący podtyp B sprawiłby mi jakieś niespodzianki? Jeśli tak, to nastąpi naruszenie LSP.
Czy zgłoszenie wyjątku jest niespodzianką? Nie całkiem. Jest to coś, co może się zdarzyć w dowolnym momencie, niezależnie od tego, czy wywołuję metodę wysyłki w OrderState, czy przyznano, czy wysłano. Więc muszę to wyjaśnić i tak naprawdę nie jest to naruszenie LSP.
To powiedziawszy , myślę, że istnieją lepsze sposoby radzenia sobie z tą sytuacją. Gdybym pisał to w C #, użyłbym interfejsów i sprawdziłbym implementację interfejsu przed wywołaniem metody. Na przykład jeśli bieżący stan zamówienia nie implementuje IShippable, nie wywołuj na nim metody wysyłki.
Ale wtedy też nie użyłbym wzoru państwa dla tej konkretnej sytuacji. Wzorzec stanu jest znacznie bardziej odpowiedni do stanu aplikacji niż do stanu obiektu domeny takiego jak ten.
Krótko mówiąc, jest to źle wymyślony przykład Wzorca Stanu i niezbyt dobry sposób radzenia sobie ze stanem zamówienia. Ale prawdopodobnie nie narusza LSP. A wzorzec państwowy sam w sobie na pewno nie.
źródło
CircleWithFixedCenterButMutableRadius
prawdopodobne naruszenie LSP, jeśli umowa konsumenckaImmutablePoint
określa, że jeśliX.equals(Y)
, konsumenci mogą swobodnie podstawić X na Y i odwrotnie, a zatem muszą powstrzymać się od używania klasy w taki sposób, że takie zastąpienie spowodowałoby kłopoty. Typ może być w stanie legalnie zdefiniowaćequals
takie, że wszystkie instancje będą porównywane jako odrębne, ale takie zachowanie prawdopodobnie ograniczy jego użyteczność.(Jest to napisane z punktu widzenia C #, więc nie zaznaczono wyjątków).
Zgodnie z artykułem Wikipedii o LSP jednym z warunków LSP jest:
Jak powinieneś to zrozumieć? Czym dokładnie są „wyjątki zgłaszane przez metody nadtypu”, gdy metody nadtypu są abstrakcyjne? Myślę, że są to wyjątki udokumentowane jako wyjątki, które mogą być zgłaszane za pomocą metod nadtypu.
Oznacza to, że jeśli
OrderState.Ship()
jest to udokumentowane jako „wyrzuca,InvalidOperationException
jeśli ten stan nie jest obsługiwany przez bieżący stan”, myślę, że ten projekt nie łamie LSP. Z drugiej strony, jeśli metody nadtypu nie zostaną w ten sposób udokumentowane, LSP zostanie naruszone.Ale to nie znaczy, że jest to dobry projekt, nie należy używać wyjątków dla normalnego przepływu sterowania, co wydaje się bardzo bliskie. Ponadto, w prawdziwej aplikacji, prawdopodobnie będziesz chciał wiedzieć, czy daną operację można wykonać, zanim spróbujesz to zrobić, na przykład, aby wyłączyć przycisk „Wyślij” w interfejsie użytkownika.
źródło