Esta imagem é obtida em Aplicando padrões e design orientado a domínio: com exemplos em C # e .NET
Este é o diagrama de classes para o Padrão de Estado em que um SalesOrder
pode ter estados diferentes durante seu tempo de vida. Apenas certas transições são permitidas entre os diferentes estados.
Agora a OrderState
classe é uma abstract
classe e todos os seus métodos são herdados para suas subclasses. Se considerarmos a subclasse Cancelled
que é um estado final que não permite nenhuma transição para outros estados, teremos que override
todos os seus métodos nesta classe para lançar exceções.
Agora, isso não viola o princípio de substituição de Liskov, uma vez que uma sublcass não deve alterar o comportamento dos pais? Mudar a classe abstrata em uma interface corrige isso?
Como isso pode ser consertado?
cancel()
muda o estado para cancelado.Respostas:
Esta implementação em particular, sim. Se você tornar os estados classes concretas em vez de implementadores abstratos, se afastará disso.
No entanto, o padrão de estado ao qual você está se referindo, que é efetivamente um design de máquina de estado, é em geral algo que discordo do modo como o vi crescer. Eu acho que há motivos suficientes para decretar isso como uma violação do princípio de responsabilidade única, porque esses padrões de gerenciamento de estado acabam sendo repositórios centrais para o conhecimento do estado atual de muitas outras partes de um sistema. Essa parte do gerenciamento de estado que está sendo centralizada geralmente exige que as regras de negócios relevantes para muitas partes diferentes do sistema as coordenem razoavelmente.
Imagine se todas as partes do sistema que se preocupam com o estado estivessem em serviços diferentes, processos diferentes em máquinas diferentes, um gerenciador de status central que detalha o status de cada um desses locais com gargalos efetivos em todo esse sistema distribuído e acho que gargalos é um sinal violação de SRP, bem como design geralmente ruim.
Por outro lado, eu sugeriria que os objetos fossem mais inteligentes, como no objeto Model no padrão MVC, onde o modelo sabe como lidar com ele mesmo, não precisa de um orquestrador externo para gerenciar o funcionamento interno ou o motivo.
Mesmo colocando um padrão de estado como esse dentro de um objeto para que ele esteja apenas se gerenciando, parece que você estaria tornando esse objeto muito grande. Os fluxos de trabalho devem ser feitos através da composição de vários objetos auto-responsáveis, eu diria, em vez de um único estado orquestrado que gerencia o fluxo de outros objetos ou o fluxo de inteligência dentro de si.
Mas, nesse ponto, é mais arte do que engenharia e, portanto, é definitivamente subjetiva sua abordagem a algumas dessas coisas, que disse que os princípios são um bom guia e sim a implementação que você listou é uma violação do LSP, mas pode ser corrigida para não ser. Apenas tenha muito cuidado com o SRP ao usar qualquer padrão dessa natureza e você provavelmente estará seguro.
fonte
Essa é uma má interpretação comum do LSP. Uma subclasse pode alterar o comportamento do pai, desde que permaneça fiel ao tipo pai.
Há uma boa e longa explicação na Wikipedia , sugerindo as coisas que violarão o LSP:
Pessoalmente, acho mais fácil simplesmente lembrar disso: se eu estiver visualizando um parâmetro em um método que possui o tipo A, alguém passando um subtipo B me causará alguma surpresa? Se eles o fizerem, há uma violação do LSP.
Lançar uma exceção é uma surpresa? Na verdade não. É algo que pode acontecer a qualquer momento, esteja eu chamando o método Ship no OrderState ou Granted ou Shipped. Então eu tenho que dar conta disso e não é realmente uma violação do LSP.
Dito isto , acho que existem maneiras melhores de lidar com essa situação. Se eu estivesse escrevendo isso em C #, usaria interfaces e verificaria a implementação de uma interface antes de chamar o método. Por exemplo, se o OrderState atual não implementar IShippable, não chame um método Ship nele.
Mas também não usaria o padrão do Estado para essa situação específica. O padrão State é muito mais apropriado ao estado de um aplicativo do que ao estado de um objeto de domínio como este.
Portanto, em poucas palavras, este é um exemplo mal elaborado do Padrão de Estado e não é uma maneira particularmente boa de lidar com o estado de uma ordem. Mas, sem dúvida, não viola o LSP. E o padrão do Estado, por si só, certamente não.
fonte
CircleWithFixedCenterButMutableRadius
provável violação do LSP se o contrato do consumidorImmutablePoint
especificar queX.equals(Y)
, se os consumidores puderem substituir X por Y livremente e vice-versa, e, portanto, devem abster-se de usar a classe de tal maneira que tal substituição possa causar problemas. O tipo pode ser capaz de definir legitimamenteequals
tal que todas as instâncias sejam comparadas como distintas, mas esse comportamento provavelmente limitaria sua utilidade.(Isso é escrito do ponto de vista do C #, portanto, não há exceções verificadas.)
Segundo o artigo da Wikipedia sobre LSP , uma das condições do LSP é:
Como você deve entender isso? O que exatamente são “exceções geradas pelos métodos do supertipo” quando os métodos do supertipo são abstratos? Eu acho que essas são as exceções documentadas como exceções que podem ser lançadas pelos métodos do supertipo.
O que isso significa é que, se
OrderState.Ship()
estiver documentado como algo como "lançaInvalidOperationException
se essas operações não forem suportadas pelo estado atual", acho que esse design não quebra o LSP. Por outro lado, se os métodos de supertipo não forem documentados dessa maneira, o LSP será violado.Mas isso não significa que este seja um bom design, você não deve usar exceções para o fluxo de controle normal, o que parece muito próximo. Além disso, em um aplicativo real, você provavelmente gostaria de saber se uma operação pode ser executada antes de tentar fazê-lo, por exemplo, para desativar o botão "Enviar" na interface do usuário.
fonte