Eu tenho algum código em que um bom modelo de herança desceu e estou tentando entender por que e como corrigi-lo. Basicamente, imagine que você tenha uma hierarquia de zoológico com:
class Animal
class Parrot : Animal
class Elephant : Animal
class Cow : Animal
etc.
Você tem seus métodos eat (), run (), etc e tudo é bom. Então, um dia, alguém aparece e diz - nossa classe CageBuilder funciona muito bem e usa o animal.weight () e animal.height (), exceto o novo bisonte africano, que é muito forte e pode quebrar a parede, então vou acrescentar mais uma propriedade para a classe Animal - isAfricanBizon () e use-a ao escolher o material e substitua-o apenas pela classe AfricanBizon. A próxima pessoa vem e faz algo semelhante e, em seguida, você sabe que possui todas essas propriedades específicas para algum subconjunto da hierarquia na classe base.
Qual é uma boa maneira de melhorar / refatorar esse código? Uma alternativa aqui seria usar apenas dynamic_casts para verificar os tipos, mas isso atrapalha os chamadores e adiciona um monte de if-then-else em todo o lugar. Você pode ter interfaces mais específicas aqui, mas se tudo o que você tem é a referência da classe base que também não ajuda muito. Alguma outra sugestão? Exemplos?
Obrigado!
fonte
Respostas:
Parece que o problema é que, em vez de implementar o RequerConcreteWall (), eles implementaram uma bandeira chamada IsAfricanBison () e depois mudaram a lógica para saber se o muro deve ou não mudar fora do escopo da classe. Suas aulas devem expor comportamento e requisitos, não identidade; seus consumidores dessas classes devem trabalhar com o que lhes é dito, não com base no que são.
fonte
isAfricanBizon () não é genérico. Suponha que você estenda sua fazenda de animais com um hipopótamo, que também é muito forte, mas retornar verdadeiro de isAfricanBizon () para ter o efeito adequado seria apenas bobo.
você sempre deseja adicionar métodos à interface que respondam a perguntas específicas; nesse caso, seria algo como força ()
fonte
strength
método pode ser consultadomaterial.canHold(animal)
, permitindo uma maneira limpa de suportar diferentes tipos de materialConcreteWall
.Acho que o seu problema é este: você tem vários clientes da biblioteca que estão interessados apenas em um subconjunto da hierarquia, mas que recebem um ponteiro / referência para a classe base. Na verdade, esse é o problema que o dynamic_cast <> existe para resolver.
É uma questão de design dos clientes para minimizar o uso de dynamic_cast <>; eles devem usá-lo para determinar se o objeto requer tratamento especial e, em caso afirmativo, todas as operações na referência reduzida.
Se você tiver coleções de funcionalidades do tipo "mix-in" que se aplicam a várias sub-hierarquias separadas, convém usar o padrão de interface que Java e C # usam; tenha uma classe base virtual que seja uma classe virtual pura e use dynamic_cast <> para determinar se uma instância fornece uma implementação para ela.
fonte
Uma coisa que você pode fazer é substituir a verificação explícita do tipo, como
isAfricanBison()
a verificação das propriedades nas quais você realmente está interessado, ou sejaisTooStrong()
.fonte
Os animais não devem se preocupar com paredes de concreto. Talvez você possa expressá-lo com valores simples.
Eu suspeito que isso não seja viável. Esse é o problema com exemplos de brinquedos, no entanto.
Eu nunca gostaria de ver RequerConcreteWalls () ou linhas e linhas de ponteiro dinâmico convertidas de qualquer forma.
Geralmente, essa é uma solução barata . É fácil de manter e conceituar. E realmente, o problema afirma que está ligado ao tipo de animal de qualquer maneira.
Isso não impede você de usar código compartilhado, apenas polui um pouco o Animal.
Mas como a gaiola é construída pode ser uma política de outro sistema, e talvez você tenha mais de um tipo de construtor de gaiolas por animal. Você pode encontrar muitas combinações estranhas e complicadas.
Eu usei o Design Baseado em Componentes para bons fins, o principal problema é que pode ser problemático quando a propriedade do Animal é compartilhada. Como evitar a destruição de destruidores é o ponto de dor.
O Double Dispatch é outra opção, embora eu sempre tenha sido reticente em entrar nele.
Além disso, é difícil adivinhar o problema.
fonte
Bem, certamente todos os animais têm a propriedade inerente de
attemptEscape()
. Enquanto alguns, o método pode representar umfalse
resultado em todos os cenários, enquanto outros podem ter uma chance com base nas heurísticas de suas outras características intrínsecas, comosize
eweight
. Então, certamente, em algum momento,attemptEscape()
torna-se trivial, pois certamente retornarátrue
.Receio não entender completamente sua pergunta ... todos os animais têm ações e características relacionadas. Alguns específicos para o animal devem ser introduzidos onde é apropriado. Tentar relacionar diretamente o Bison ao Parrots não é uma boa configuração de herança e não deve ser realmente um problema em um design adequado.
fonte
Outra opção seria usar uma fábrica que cria gaiolas apropriadas para cada animal. Eu acho que isso pode ser melhor caso as condições sejam muito diferentes para cada um deles. Mas se for apenas essa condição, o
RequiresConcreteWall()
método mencionado acima fará isso.fonte
que tal RecommendCageType () como opção para RequerConcreteWall ()
fonte
Por que não fazer algo assim
class Animals { /***/ } class HeavyAnimals{} : Animals //The basic class for animals like the African Bison
Com a classe HeavyAnimals, você pode criar a classe African Bison estendendo a classe HeavyAnimals.
Então agora você pode usar a classe pai (os animais) para criar outra classe base, como a classe HeavyAnimal, para criar a classe Bison africano e outros animais pesados. Portanto, com o bisonte africano, agora você tem acesso aos métodos e propriedades da classe Animal (esta é a base para todos os animais) e acesso à classe HeavyAnimals (essa é a base dos animais pesados)
fonte