Problema de design de inheritance múltipla em Java

Como você lida com a inheritance única em java? Aqui está o meu problema específico:

Eu tenho três classs (simplificadas):

public abstract class AbstractWord{ String kind; // eg noun, verb, etc public String getKind(){ return kind; } } public class Word extends AbstractWord{ public final String word; ctor... public void setKind(){ // based on the variable word calculate kind.. } } public class WordDescriptor extends AbstractWord{ ctor.. public void setKind(String kind){this.kind = kind;} } 

Isto é o que eu considero minha implementação mais básica, mas eu quero fazer outras implementações.

Digamos que eu queira adicionar uma nova variável, word long, mas quero adicioná-la usando inheritance. Significado eu não quero modificar essa class original do AbstractWord. Ou seja, algo ao longo das linhas deste:

 public class Length{ private int length; public int getLength(){return length}; } public class BetterWord extends AbstractWord AND Length{ public void setLength(){ // based on the variable word calculate Length.. } } public class BetterWordDescriptor extends AbstractWord AND length{ public void setLength(int length){this.length = length;} } 

Eu sei que o java não me deixa fazer isso, mas tornou meu código muito feio. Agora, sempre que eu adiciono um campo, estou apenas adicionando-o ao AbstractWord, mas então eu preciso renomear esse AbstractWord (e Word e WordDescriptor). (Eu não posso simplesmente adicionar o campo ao outro por causa da compatibilidade com versões anteriores, quebra igual a methods e coisas assim).

Este parece ser um problema de design bastante comum, mas eu tenho quebrado a minha cabeça e não consigo encontrar nenhuma solução bonita.

Existe um padrão de design que aborda isso? Eu tenho algumas soluções potenciais, mas eu queria ver se havia algo que estava faltando.

obrigada, Jake

Atualização: Comprimento refere-se ao número de sílabas na palavra (desculpe pela falta de clareza)

Favorecer a composição sobre inheritance.

A solução leva em consideração que pode haver outro tipo de palavra que pode precisar do WordLengthSupport.

Da mesma forma, outras interfaces podem ser criadas e implementadas, e vários tipos de palavras podem ter a combinação e a correspondência dessas interfaces.

.

 public class WordLength { private int length = 0; public int getLength(){return length}; public void setLength(int length){this.length = length}; } 

.

 public interface WordLengthSupport { public WordLength getWordLength(); } 

.

 public class BetterWord extends AbstractWord implements WordLengthSupport { WordLength wordLength; public WordLength getWordLength() { if(wordLength==null) { // each time word changes // make sure to set wordLength to null calculateWordLength(); } return wordLength; } private void calculateWordLength() { // This method should be // called in constructor // or each time word changes int length = // based on the variable word calculate Length.. this.wordLength = new WordLength(); this.wordLength.setLength(length); } } 

.

 public class BetterWordDescriptor extends AbstractWord implements WordLengthSupport { WordLength wordLength; public WordLength getWordLength(return wordLength); public void setWordLength(WordLength wordLength) { // Use this to populate WordLength of respective word this.wordLength = wordLength; } } 

.

O Strategy Pattern define uma família de algoritmos, encapsula cada um deles e os torna intercambiáveis. A estratégia permite que o algoritmo varie independentemente dos clientes que o usam.

Essa solução não usa o padrão de estratégia, mas pode ser refatorada para o mesmo.

Apenas use composição em vez de inheritance:

um BetterWord é um AbstractWord que tem um Length :

 public class BetterWord extends AbstractWord { private Length length; public void setLength(int value){ length.setLength(value); } } 

EDITAR

Se a API precisar de um object do tipo Comprimento, basta adicionar um getter:

 public class BetterWord extends AbstractWord { private Length length; public void setLength(int value){ length.setLength(value); } public Length getLength() { return length } } 

Ou renomeie a implementação Length para LengthImpl e defina uma interface Length , porque uma class pode implementar várias interfaces.

Com seu exemplo específico, você poderia usar o padrão decorador em conjunto com interfaces para complementar sua class do Word com funcionalidade adicional; por exemplo

 // *Optional* interface, useful if we wish to reference Words along with // other classs that support the concept of "length". public interface Length { int getLength(); } // Decorator class that wraps another Word and provides additional // functionality. Could add any additional fields here too. public class WordExt extends AbstractWord implements Length { private final Word word; public class(Word word) { this.word = word; } public int getLength() { return word.getKind().length(); } } 

Além disso, é importante notar que a falta de inheritance múltipla em Java não é realmente o problema aqui; é mais um caso de retrabalhar seu design. Em geral, considera-se uma prática ruim sobrecarregar a inheritance, pois as hierarquias de inheritance profunda são difíceis de interpretar / manter.

Olhando para isso, meu primeiro sentimento é que seu modelo é um pouco complicado.

Uma palavra tem uma String para descrever a própria palavra sendo armazenada no object Word junto com uma class para dizer que é um substantivo, verbo, adjetivo etc. Outra propriedade de um Word é o tamanho da string armazenada no object Word.

Pense nas coisas em termos de relacionamentos “é-um” e “tem-um” e você pode remover muita complexidade.

Por exemplo, por que você precisa de um WordDescriptor que estenda o AbstractWord? Uma palavra vai mudar de um verbo para um adjetivo? Eu teria pensado que o tipo de palavra foi definido quando o object foi criado e não iria mudar durante a vida útil do object do Word. Uma vez que você tivesse um object Word para a palavra “Austrália”, o tipo de palavra não mudaria durante a vida útil do object.

Hmmm Talvez você possa ter um object Word representando a palavra “bark” após instanciar o object com um tipo de “verbo” para descrever o som que um cão faz. Então você percebe que realmente precisava ter o object Word para representar um substantivo que descreve a cobertura de uma tree. Possível, mas tanto a casca do cachorro como a casca da tree podem existir.

Então, acho que o modelo que você escolheu é um pouco complicado demais e que sua pergunta pode ser resolvida voltando e simplificando seu modelo de object original.

Comece fazendo uma pergunta para cada um dos aspectos da inheritance do seu modelo básico.

Quando digo que a Classe B estende a Classe A, posso dizer que a Classe B “é -a” Classe A e que estou especializando seu comportamento?

Por exemplo, uma class de base Animal pode ser estendida para fornecer a class especializada de canguru. Então você pode dizer que “o canguru” é um “Animal”. Você está especializando o comportamento.

Então olhe para os atributos, um Kangaroo tem um atributo Location para descrever onde ele é encontrado. Então você pode dizer um Kangaroo “tem um” local. Um canguru “é-um” local não faz sentido.

Da mesma forma, uma palavra “tem um” comprimento. E a afirmação de que uma palavra “é-um” não faz sentido.

BTW Todas as referências australianas neste post são deliberadas para comemorar o Dia da Austrália, que é hoje 26 de janeiro!

HTH

(Eu não posso simplesmente adicionar o campo ao outro por causa da compatibilidade com versões anteriores, quebra igual a methods e coisas assim).

Não vai quebrar a compatibilidade da fonte. Não a menos que você esteja fazendo algo realmente louco em seus methods de igualdade.

E renomear suas classs geralmente não é a maneira de lidar com compatibilidade binária.

O problema não é “como lidar com inheritance única”. O que você está perdendo não é realmente um padrão de design, mas sim aprender a projetar a API separadamente da implementação.

Eu implementaria da seguinte forma:

 public interface WordDescriptor { void getKind(); Word getWord(); } public interface Word { String getWord(); } public class SimpleWord implements Word { private String word; public SimpleWord(String word) { this.word = word; } public String getWord() { return word; } } public class SimpleWordDescriptor implements WordDescriptor { private Word word; private String kind; public SimpleWordDescriptor(Word word, String kind) { this.word = word; this.kind = kind; // even better if WordDescriptor can figure it out internally } public Word getWord() { return word; } public String getKind() { return kind; } } 

Com essa configuração básica, quando você quiser introduzir uma propriedade length, tudo o que você precisa fazer é:

 public interface LengthDescriptor { int getLength(); } public class BetterWordDescriptor extends SimpleWordDescriptor implements LengthDescriptor { public BetterWordDescriptor(Word word, String kind) { super(word, kind); } public int getLength() { getWord().length(); } } 

As outras respostas que usam composição de propriedades, bem como o padrão Decorator, também são soluções inteiramente válidas para o seu problema. Você só precisa identificar quais são seus objects e como eles são “composable” e como eles devem ser usados ​​- portanto, projetando a API primeiro.

/ ** * Primeiro exemplo * /

 class FieldsOfClassA { public int field1; public char field2; } interface IClassA { public FieldsOfClassA getFieldsA(); } class CClassA implements IClassA { private FieldsOfClassA fields; @Override public FieldsOfClassA getFieldsA() { return fields; } } /** * seems ok for now * but let's inherit this sht */ class FieldsOfClassB { public int field3; public char field4; } interface IClassB extends IClassA { public FieldsOfClassA getFieldsA(); public FieldsOfClassB getFieldsB(); } class CClassB implements IClassB { private FieldsOfClassA fieldsA; private FieldsOfClassB fieldsB; @Override public FieldsOfClassA getFieldsA() { return fieldsA; } @Override public FieldsOfClassB getFieldsB() { return fieldsB; } } 

/ **

  • wow esse monstro ficou maior

  • imagine que você vai precisar de 4 lvl de inheritance

  • levaria muito tempo para escrever esse inferno

  • Eu nem estou falando que o usuário daqueles iface vai pensar

  • quais campos eu precisarei de camposA camposB camposC ou outro

Então composição não funciona aqui e suas tentativas patéticas são inúteis

Quando você pensa sobre programação orientada a objects

você precisa de modelos BIG com 6-7 lvls de inheritance múltipla

porque isso é bom teste e porque corresponde a modelos da vida real ou modelos matemáticos testados pela civilização por 4 mil anos.

Se seus modelos requerem 2 lvl de inheritance pare de fingir que você está usando OO

U pode facilmente implementá-lo com qualquer linguagem, mesmo um procedimento como C ou Basic language * /