A Circle
extensão deEllipse
interrupções quebra o Princípio da Substituição de Liskov , porque modifica uma pós-condição: ou seja, você pode definir X e Y independentemente para desenhar uma elipse, mas X sempre deve ser igual a Y para círculos.
Mas o problema aqui não é causado pelo fato de o Circle ser o subtipo de uma elipse? Não poderíamos reverter o relacionamento?
Portanto, Circle é o supertipo - ele tem um método único setRadius
.
Em seguida, o Ellipse estende o Círculo adicionando setX
e setY
. A chamada setRadius
do Ellipse definiria X e Y - o que significa que a pós-condição no setRadius é mantida, mas agora você pode definir X e Y independentemente por meio de uma interface estendida.
object-oriented
solid
liskov-substitution
HorusKol
fonte
fonte
Respostas:
O problema com isso (e o problema do quadrado / retângulo) está assumindo falsamente que um relacionamento em um domínio (geometria) se mantém em outro (comportamento)
Um círculo e elipse estão relacionados se você os estiver visualizando através do prisma da teoria geométrica. Mas esse não é o único domínio que você pode olhar.
O design orientado a objetos lida com o comportamento .
A característica definidora de um objeto é o comportamento pelo qual o objeto é responsável. E no domínio do comportamento, um círculo e uma elipse têm um comportamento tão diferente que provavelmente é melhor não pensar neles como relacionados. Nesse domínio, uma elipse e um círculo não têm relacionamento significativo.
A lição aqui é escolher o domínio que faz mais sentido para OOD, não tentar calçar um relacionamento simplesmente porque ele existe em um domínio diferente.
O exemplo mais comum desse erro no mundo real é assumir que os objetos estão relacionados (ou até a mesma classe) porque eles têm dados semelhantes, mesmo que seu comportamento seja muito diferente. Esse é um problema comum quando você começa a construir objetos "dados primeiro", definindo para onde vão os dados. Você pode acabar com uma classe relacionada a dados com comportamento completamente diferente. Por exemplo, os objetos de salário e de funcionário podem ter um atributo "salário bruto", mas um funcionário não é um tipo de salário e um salário não é um tipo de empregado.
fonte
Os círculos são um caso especial de elipse, a saber, que os dois eixos da elipse são iguais. É fundamentalmente falso no domínio do problema (geometria) afirmar que as elipses podem ser um tipo de círculo. O uso desse modelo defeituoso violaria muitas garantias de um círculo, por exemplo "todos os pontos do círculo têm a mesma distância do centro". Isso também seria uma violação do Princípio de Substituição de Liskov. Como uma elipse teria um único raio? (Não é
setRadius()
mais importantegetRadius()
)Embora a modelagem de círculos como um subtipo de elipses não esteja fundamentalmente errada, é a introdução da mutabilidade que quebra esse modelo. Sem os métodos
setX()
esetY()
, não há violação do LSP. Se for necessário ter um objeto com dimensões diferentes, criar uma nova instância é uma solução melhor:fonte
Ellipse
eCircle
(comogetArea
) que seria abstraída para um tipoShape
- poderiaEllipse
eCircle
separadamente subtipoShape
e satisfazia o LSP?É um erro desde o início insistir em ter uma classe "Elipse" e "Círculo", onde uma é uma subclasse da outra. Você tem duas opções realistas: uma é ter aulas separadas. Eles podem ter uma superclasse comum, para coisas como cor, se o objeto está preenchido, largura da linha para desenhar etc.
A outra é ter apenas uma classe chamada "Ellipse". Se você tem essa classe, é fácil usá-la para representar círculos (pode haver armadilhas dependendo dos detalhes da implementação; um Ellipse terá algum ângulo e o cálculo desse ângulo não deve causar problemas para uma elipse em forma de círculo). Você poderia até ter métodos especializados para elipses circulares, mas essas "elipses circulares" ainda seriam objetos completos de "Elipse".
fonte
Cormac tem uma resposta realmente ótima, mas eu só quero elaborar um pouco sobre o motivo da confusão em primeiro lugar.
A herança em OO geralmente é ensinada usando metáforas do mundo real, como "maçãs e laranjas são subclasses de frutas". Infelizmente, isso leva à crença equivocada de que os tipos de OO devem ser modelados de acordo com algumas hierarquias taxonômicas existentes independentemente do programa.
Porém, no design de software, os tipos devem ser modelados de acordo com os requisitos do aplicativo. Classificações em outros domínios são geralmente irrelevantes. Em uma aplicação real com objetos "Apple" e "Orange" - digamos, um sistema de gerenciamento de inventário para um supermercado - eles provavelmente não serão classes distintas, e categorias como "Frutas" serão atributos e não supertipos.
O problema da elipse circular é um arenque vermelho. Na geometria, um círculo é uma especialização de uma elipse, mas as classes no seu exemplo não são figuras geométricas. Fundamentalmente, figuras geométricas não são mutáveis. Eles podem ser transformados , mas um círculo pode ser transformado em uma elipse. Portanto, um modelo em que os círculos podem alterar o raio, mas não as reticências, não corresponde à geometria. Esse modelo pode fazer sentido em um aplicativo específico (por exemplo, uma ferramenta de desenho), mas a classificação geométrica é irrelevante para a maneira como você projeta a hierarquia de classes.
Então, Circle deve ser uma subclasse de Ellipse ou vice-versa? Depende totalmente dos requisitos do aplicativo em particular que utiliza esses objetos. Um aplicativo de desenho pode ter diferentes opções em como tratar círculos e elipses:
Trate círculos e elipses como tipos distintos de formas com diferentes interfaces de usuário (por exemplo, duas alças de redimensionamento em uma elipse, uma alça em um círculo). Isso significa que você pode ter uma elipse que é geometricamente um círculo, mas não um círculo da perspectiva do aplicativo.
Trate todas as elipses, incluindo círculos da mesma forma, mas tenha a opção de "travar" x e y com o mesmo valor.
Elipses são apenas círculos nos quais uma transformação de escala foi aplicada.
Cada design possível levará a um modelo de objeto diferente -
No 1º caso, Circle e Ellipses serão irmãos classes
No segundo, não haverá nenhuma classe Circle distinta
No terceiro, não haverá uma classe Ellipse distinta. Portanto, o chamado problema da elipse circular não aparece na imagem em nenhum deles.
Então, para responder à pergunta da seguinte maneira: O círculo deve estender a elipse? A resposta é: depende do que você quer fazer com isso. Mas provavelmente não.
fonte
Seguindo os pontos do LSP, uma solução 'adequada' para esse problema é que @HorusKol e @Ixrec surgiram - derivando os dois tipos do Shape. Mas depende do modelo do qual você está trabalhando, portanto, você deve sempre voltar a isso.
O que eu fui ensinado é:
Se o subtipo não puder executar o mesmo comportamento que o supertipo, o relacionamento não se mantém na premissa do IS-A - ele deve ser alterado.
Em inglês:
(Exemplo:
É assim que a classificação funciona (ou seja, no mundo animal) e, principalmente, no OO.
Usando isso como a definição de herança e polimorfismo (que sempre são escritos juntos), se esse princípio for quebrado, você deve tentar repensar os tipos que está tentando modelar.
Conforme mencionado por @HorusKul e @Ixrec, em matemática você definiu claramente os tipos. Mas, em matemática, um círculo é uma elipse porque é um SUBSET da elipse. Mas no POO não é assim que a herança funciona. Uma classe só deve herdar se for um SUPERSET (uma extensão) de uma classe existente - ou seja, ainda é a classe base em todos os contextos.
Com base nisso, acho que a solução deve ser um pouco reformulada.
Tenha um tipo de base Shape e RoundedShape (efetivamente um círculo, mas eu usei um nome diferente aqui DELIBERATELY ...)
... então Ellipse.
Dessa maneira:
(Isso agora faz sentido para as pessoas na linguagem. Já temos um conceito claramente definido de 'círculo' em nossas mentes, e o que estamos tentando fazer aqui ao generalizar (agregar) quebra esse conceito.)
fonte
De uma perspectiva OO, a elipse estende o círculo, especializando-se adicionando algumas propriedades. As propriedades existentes do círculo ainda se mantêm na elipse, apenas se tornam mais complexas e mais específicas. Não vejo nenhum problema com o comportamento nesse caso, como o Cormac faz, as formas não têm comportamento. O único problema é que, no sentido liguístico ou matemático, não parece correto dizer "uma elipse É UM círculo". Porque todo o objetivo do exercício que não é mencionado, mas está implícito, era classificar formas geométricas. Essa pode ser uma boa razão para considerar círculo e elipse como pares, não vinculá-los por herança e aceitar que eles possuem algumas das mesmas propriedades e NÃO deixar sua mente distorcida de OO seguir essa observação.
fonte