Herança deu errado


12

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!


@ James: então você terá que escrever analisadores à mão. : S
Matteo Italia

5
Claramente, este é um caso de exigência de cliente ridícula. Não há bisonte na África. Você não pode projetar modelos de objetos que não tenham conexão com a realidade. A menos que essa realidade seja criada por mãos cheias de dólares. O que resolve o problema.
Hans Passant

1
Coma todo o bisonte? [Eu postei isso anteriormente mas foi por algum motivo excluído, presumivelmente por idiotas sem humor.]
James McNellis

O CageBuilder precisa de sua própria classe? E se houver um método MakeCage padrão, que possa ser substituído por cada classe individual.
Job

1
Você menciona a confusão de if-then-else como uma desvantagem para os chamadores, mas assim que os chamadores começam a usar isAfricanBizon () eles confundem o código com if-then-else automaticamente. Portanto, é desorganização if-then-else com isAfricanBizon () ou desorganização if-then-else com elencos dinâmicos.
Davidk01

Respostas:


13

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.


1
-1: Apenas diz o que não fazer. O OP já sabe que essa foi uma má idéia, daí a questão.
Steven Evers

12

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 ()


+1: todo mundo parece estar quebrando o modelo conceitual da classe (que apenas encapsula propriedades de diferentes tipos de animais), para acomodar esse caso de uso específico. Um strengthmétodo pode ser consultado material.canHold(animal), permitindo uma maneira limpa de suportar diferentes tipos de material ConcreteWall.
113711 Aidan Cully

Gosto mais da abordagem de propriedade Strength () do que a sugestão de RequirementsConcreteWall () de outros, porque é mais flexível para permitir requisitos futuros. Para iniciantes, faça a classe CageBuilder decidir quais materiais são fortes o suficiente e, em seguida, você pode estender facilmente a classe com novos materiais.
Jhocking 17/07

3

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.


1

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 seja isTooStrong().


1
isTooStrong () para quê? Você está adicionando código específico da gaiola à classe de animais.
Steven Evers

1

Os animais não devem se preocupar com paredes de concreto. Talvez você possa expressá-lo com valores simples.

class Animal {
public:
  virtual ~Animal() {}
  virtual size_t height() const = 0;
  virtual size_t weight() const = 0;
  virtual bool isStrong() const = 0;
};

Cage *CreateCageFromSQL(Animal &a);
Cage *CreateCageFromOrangePeelsAndSticks(Animal &a);

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.

class Animal {
public:
  virtual ~Animal() {}
  virtual CageBuilder *getCageBuilder() = 0;
};

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.


0

Bem, certamente todos os animais têm a propriedade inerente de attemptEscape(). Enquanto alguns, o método pode representar um falseresultado em todos os cenários, enquanto outros podem ter uma chance com base nas heurísticas de suas outras características intrínsecas, como sizee weight. 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.


-1

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.


-1

que tal RecommendCageType () como opção para RequerConcreteWall ()


-2

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)


2
Isso pode funcionar como uma combinação ou característica, mas certamente não como uma subclasse. Isso está implorando por herança múltipla na próxima vez que outra propriedade for necessária.
Ordous 17/07/2015
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.