Herança Múltipla de Java


168

Na tentativa de entender completamente como resolver os vários problemas de herança do Java, tenho uma pergunta clássica que preciso ser esclarecida.

Vamos dizer que eu tenho classe Animalisto tem subclasses Birde Horsee eu preciso fazer uma classe Pegasusque se estende a partir Birde Horseuma vez Pegasusé ao mesmo tempo um pássaro e um cavalo.

Eu acho que esse é o problema clássico dos diamantes. Pelo que entendi, a maneira clássica de resolver isso é criar as interfaces Animal, Birde Horseclasses e implementar a Pegasuspartir delas.

Fiquei me perguntando se havia outra maneira de resolver o problema em que ainda posso criar objetos para pássaros e cavalos. Se houvesse uma maneira de criar animais, isso seria ótimo, mas não necessário.


6
Eu acho que você pode criar manualmente as classes e armazená-las como membros (composição em vez de herança). Com a classe Proxy ( docs.oracle.com/javase/7/docs/api/java/lang/reflect/Proxy.html ), essa pode ser uma opção, embora você também precise das interfaces.
Gábor Bakos 17/02

4
A @RAM não deve estender o Bird, mas sim um comportamento que lhe permita voar. : D problema resolvido
Yogesh

11
Exatamente. Que tal uma interface CanFly. :-)
Surprised Coconut

28
Eu acho que essa é a abordagem errada. Você tem animais - cavalos, pássaros. E você tem propriedades - Voando, Carnívoro. Um Pegasus não é um HorseBird, é um cavalo que pode voar. As propriedades devem estar em interfaces. Então public class Pegasus extends Horse implements Flying.
Boris the Spider

12
Entendo por que você acha que está errado e não segue as regras da biologia e aprecia sua preocupação, mas no que diz respeito ao programa que preciso construir, que realmente tem a ver com bancos, essa foi a melhor abordagem para mim. Como não queria postar meus problemas reais, uma vez que isso seria contra as regras, mudei um pouco o exemplo. Obrigado embora ...
Sheli 17/02

Respostas:


115

Você pode criar interfaces para classes de animais (classe no significado biológico), como public interface Equidaepara cavalos e public interface Avialaepássaros (não sou biólogo, portanto os termos podem estar errados).

Então você ainda pode criar um

public class Bird implements Avialae {
}

e

public class Horse implements Equidae {}

e também

public class Pegasus implements Avialae, Equidae {}

Adicionando a partir dos comentários:

Para reduzir o código duplicado, você pode criar uma classe abstrata que contenha a maior parte do código comum dos animais que você deseja implementar.

public abstract class AbstractHorse implements Equidae {}

public class Horse extends AbstractHorse {}

public class Pegasus extends AbstractHorse implements Avialae {}

Atualizar

Eu gostaria de adicionar mais um detalhe. Como Brian observa , isso é algo que o OP já sabia.

No entanto, quero enfatizar que sugiro ignorar o problema da "herança múltipla" com interfaces e que não recomendo o uso de interfaces que já representem um tipo concreto (como Bird), mas mais um comportamento (outras se referem a digitação de pato, o que também é bom, mas quero dizer apenas: a classe biológica dos pássaros, Avialae). Também não recomendo o uso de nomes de interface que começam com um 'I' maiúsculo, como, por exemplo IBird, que não diz nada sobre o motivo de você precisar de uma interface. Essa é a diferença para a pergunta: construa a hierarquia de herança usando interfaces, use classes abstratas quando útil, implemente classes concretas quando necessário e use delegação, se apropriado.


9
Que ... é exatamente o que o OP diz que sabe que pode fazer na Q.
Brian Roach

4
Como o Pegasus já é um cavalo (que voa), acho que você poderia reutilizar mais código se estender o cavalo e implementar o Avialae.
Pablo Lozano

8
Não tenho certeza, ainda não vi um Pegasus. No entanto, nesse caso, eu preferiria usar um AbstractHorse, que também pode ser usado para construir zebras ou outros animais semelhantes a cavalos.
Moritz Petersen

5
@MoritzPetersen Se você realmente deseja reutilizar abstrações e dar nomes significativos, provavelmente AbstractEquidaeseria mais adequado do que AbstractHorse. Seria estranho ter uma zebra estendendo um cavalo abstrato. Bela resposta, a propósito.
afsantos

3
Implementar a digitação com patos também envolveria uma Duckimplementação de classe Avialae?
mgarciaisaia

88

Existem duas abordagens fundamentais para combinar objetos:

  • O primeiro é a herança . Como você já identificou as limitações da herança, significa que você não pode fazer o que precisa aqui.
  • O segundo é composição . Como a herança falhou, você precisa usar a composição.

A maneira como isso funciona é que você tem um objeto Animal. Nesse objeto, você adiciona outros objetos que fornecem as propriedades e comportamentos necessários.

Por exemplo:

  • Pássaro estende implementos animais IFlier
  • Cavalo estende implementos animais IHerbivore, IQuadruped
  • Pegasus estende implementos animais IHerbivore, IQuadruped, IFlier

Agora, IFlierfica assim:

 interface IFlier {
     Flier getFlier();
 }

Então Birdé assim:

 class Bird extends Animal implements IFlier {
      Flier flier = new Flier();
      public Flier getFlier() { return flier; }
 }

Agora você tem todas as vantagens da herança. Você pode reutilizar o código. Você pode ter uma coleção de IFliers e pode usar todas as outras vantagens do polimorfismo, etc.

No entanto, você também tem toda a flexibilidade do Composition. Você pode aplicar quantas interfaces diferentes e classe de apoio composto desejar para cada tipo de Animal- com o controle necessário sobre a configuração de cada bit.

Padrão estratégico Estratégia de abordagem alternativa à composição

Uma abordagem alternativa, dependendo do que e como você está fazendo, é fazer com que a Animalclasse base contenha uma coleção interna para manter a lista de comportamentos diferentes. Nesse caso, você acaba usando algo mais próximo do padrão de estratégia. Isso oferece vantagens em termos de simplificação do código (por exemplo, Horsenão é necessário saber nada sobre Quadrupedou Herbivore), mas se você também não faz a abordagem da interface, perde muitas das vantagens do polimorfismo, etc.


Uma alternativa semelhante também pode ser o uso de classes de tipo, embora essas não sejam tão naturais para uso em Java (você precisa usar métodos de conversão e assim por diante), esta introdução pode ser útil para se ter uma idéia: typeclassopedia.bitbucket.org
Gábor Bakos

1
Esta é uma solução muito melhor que a abordagem recomendada na resposta aceita. Por exemplo, se mais tarde eu quiser adicionar "cor do bico" à interface do Bird, eu tenho um problema. Pegasus é um composto, que leva elementos de cavalos e pássaros, mas nem totalmente cavalo nem pássaro. Usar composição faz todo sentido nesse cenário.
JDB ainda se lembra de Monica

Mas getFlier()deve ser reimplementado para todo e qualquer tipo de pássaro.
SMUsamaShah

1
@ LifeH2O Divida-os em blocos de funcionalidade compartilhada e conceda-lhes isso. ou seja, você pode ter MathsTeachere EnglishTeacherambos herdar Teacher, ChemicalEngineer, MaterialsEngineeretc herdar Engineer. Teachere Engineerambos implementam Component. O Personentão só tem uma lista de Components, e você pode dar a eles os Components certos para isso Person. ou seja person.getComponent(Teacher.class), person.getComponent(MathsTeacher.class)etc.
Tim B

1
Esta é a melhor resposta. Como regra geral, herança representa "é" e uma interface representa "pode". Um Pegasus é um animal que pode voar e andar. Um pássaro é um animal que pode voar. Um cavalo é um animal que pode andar.
Robear 17/03/16

43

Eu tenho uma ideia estúpida:

public class Pegasus {
    private Horse horseFeatures; 
    private Bird birdFeatures; 

   public Pegasus(Horse horse, Bird bird) {
     this.horseFeatures = horse;
     this.birdFeatures = bird;
   }

  public void jump() {
    horseFeatures.jump();
  }

  public void fly() {
    birdFeatures.fly();
  }
}

24
Vai funcionar, mas não gosto desse tipo de abordagem (Wrappper), porque parece que Pegasus TEM um cavalo, ao invés disso, é um cavalo.
Pablo Lozano

Obrigado. Não pude deixar de postar a ideia. Eu meio que sei que é estúpido, mas eu não ver porque é estúpido ...
Pavel Janicek

3
É quase como uma versão não recondicionada da abordagem de composição da resposta de Tim B.
21414 Stephan Stephan

1
Essa é mais ou menos a maneira aceita de fazer isso, embora você também deva ter algo como IJumps com um método "jump" implementado pelo Horse e Pegasus e IFlies com um método "fly" implementado pelo Bird e Pegasus.
MatsT 17/02

2
@ Pablo, um Pegasus TEM horseFeatures e TEM birdFeatures. +1 para a resposta, pois mantém o código simples, diferentemente das soluções Java desajeitadas, geradoras de classe e adequadas.
Jane Goodall

25

Posso sugerir o conceito de digitação de pato ?

Muito provavelmente você tenderia a fazer o Pegasus estender uma interface Bird e Horse, mas a digitação de pato na verdade sugere que você deve herdar um comportamento . Como já foi dito nos comentários, um pégaso não é um pássaro, mas pode voar. Portanto, seu Pegasus deve herdar uma Flyableinterface e digamos umGallopable interface.

Esse tipo de conceito é utilizado no Padrão de Estratégia . O exemplo dado realmente mostra como um pato herda o FlyBehavioure QuackBehavioure ainda pode haver patos, por exemplo, o RubberDuckque não pode voar. Eles também poderiam ter Duckestendido a Birdclasse -classe, mas teriam desistido de alguma flexibilidade, porque todos Duckseriam capazes de voar, até os pobres RubberDuck.


19

Tecnicamente falando, você pode estender apenas uma classe de cada vez e implementar várias interfaces, mas ao colocar a mão na engenharia de software, prefiro sugerir uma solução específica para um problema que geralmente não é responsável. A propósito, é uma boa prática OO, não estender classes concretas / apenas estender classes abstratas para evitar comportamentos indesejados de herança - não existe um "animal" e nenhum uso de um objeto animal, mas apenas animais concretos.


13

No Java 8, que ainda está na fase de desenvolvimento em fevereiro de 2014, você pode usar métodos padrão para obter uma espécie de herança múltipla semelhante a C ++. Você também pode dar uma olhada neste tutorial, que mostra alguns exemplos que devem ser mais fáceis de começar a trabalhar do que a documentação oficial.


1
Esteja ciente de que se seu Bird e Horse tiverem um método padrão, você ainda encontrará o problema do diamante e precisará implementá-lo separadamente em sua classe Pegasus (ou obter um erro do compilador).
Mikkel Løkke 17/02

@ Mikkel Løkke: A classe Pegasus precisa definir substituições para os métodos ambíguos, mas pode implementá-los apenas delegando a qualquer um dos métodos (ou ambos em uma ordem escolhida).
Holger

12

É seguro manter um cavalo em um estábulo com meia porta, pois um cavalo não pode passar de meia porta. Por isso, montei um serviço de alojamento para cavalos que aceita qualquer item do tipo cavalo e o coloco em um estábulo com meia porta.

Então, um cavalo é como um animal que pode voar até um cavalo?

Eu costumava pensar muito em herança múltipla, mas agora que programa há mais de 15 anos, não me importo mais em implementar herança múltipla.

Freqüentemente, quando tentei lidar com um design que apontava para várias heranças, mais tarde cheguei à conclusão de que não entendi o domínio do problema.

OU

Se parece um pato e grasna como um pato, mas precisa de baterias, você provavelmente tem a abstração errada .


Se eu li sua analogia corretamente, você está dizendo que, nas interfaces iniciais, parece ótimo, pois elas permitem solucionar problemas de design, por exemplo, você pode usar a API de outra pessoa forçando suas classes através das interfaces. Mas, depois de alguns anos, você percebeu que o problema era um design ruim para começar.
CS

8

Java não tem um problema de herança múltipla, pois não possui herança múltipla. Isso ocorre por design, a fim de resolver o problema real de herança múltipla (O problema do diamante).

Existem diferentes estratégias para mitigar o problema. O mais imediatamente possível é o objeto Composite que Pavel sugere (essencialmente como o C ++ lida com ele). Não sei se a herança múltipla via linearização C3 (ou similar) está nos cartões para o futuro do Java, mas duvido.

Se sua pergunta é acadêmica, a solução correta é que Pássaro e Cavalo são mais concretos e é falso supor que um Pegasus é simplesmente um Pássaro e um Cavalo combinados. Seria mais correto dizer que um Pegasus tem certas propriedades intrínsecas em comum com Pássaros e Cavalos (ou seja, eles talvez tenham ancestrais comuns). Isso pode ser suficientemente modelado como aponta a resposta de Moritz.


6

Eu acho que depende muito das suas necessidades e de como suas classes de animais devem ser usadas no seu código.

Se você quiser usar métodos e recursos de suas implementações de cavalos e aves em sua classe Pegasus, poderá implementar o Pegasus como uma composição de um pássaro e um cavalo:

public class Animals {

    public interface Animal{
        public int getNumberOfLegs();
        public boolean canFly();
        public boolean canBeRidden();
    }

    public interface Bird extends Animal{
        public void doSomeBirdThing();
    }
    public interface Horse extends Animal{
        public void doSomeHorseThing();
    }
    public interface Pegasus extends Bird,Horse{

    }

    public abstract class AnimalImpl implements Animal{
        private final int numberOfLegs;

        public AnimalImpl(int numberOfLegs) {
            super();
            this.numberOfLegs = numberOfLegs;
        }

        @Override
        public int getNumberOfLegs() {
            return numberOfLegs;
        }
    }

    public class BirdImpl extends AnimalImpl implements Bird{

        public BirdImpl() {
            super(2);
        }

        @Override
        public boolean canFly() {
            return true;
        }

        @Override
        public boolean canBeRidden() {
            return false;
        }

        @Override
        public void doSomeBirdThing() {
            System.out.println("doing some bird thing...");
        }

    }

    public class HorseImpl extends AnimalImpl implements Horse{

        public HorseImpl() {
            super(4);
        }

        @Override
        public boolean canFly() {
            return false;
        }

        @Override
        public boolean canBeRidden() {
            return true;
        }

        @Override
        public void doSomeHorseThing() {
            System.out.println("doing some horse thing...");
        }

    }

    public class PegasusImpl implements Pegasus{

        private final Horse horse = new HorseImpl();
        private final Bird bird = new BirdImpl();


        @Override
        public void doSomeBirdThing() {
            bird.doSomeBirdThing();
        }

        @Override
        public int getNumberOfLegs() {
            return horse.getNumberOfLegs();
        }

        @Override
        public void doSomeHorseThing() {
            horse.doSomeHorseThing();
        }


        @Override
        public boolean canFly() {
            return true;
        }

        @Override
        public boolean canBeRidden() {
            return true;
        }
    }
}

Outra possibilidade é usar um sistema de componentes de entidade abordagem de vez de herança para definir seus animais. Obviamente, isso significa que você não terá classes Java individuais dos animais, mas elas serão definidas apenas por seus componentes.

Algum pseudocódigo para uma abordagem de sistema de componentes de entidade pode ser assim:

public void createHorse(Entity entity){
    entity.setComponent(NUMER_OF_LEGS, 4);
    entity.setComponent(CAN_FLY, false);
    entity.setComponent(CAN_BE_RIDDEN, true);
    entity.setComponent(SOME_HORSE_FUNCTIONALITY, new HorseFunction());
}

public void createBird(Entity entity){
    entity.setComponent(NUMER_OF_LEGS, 2);
    entity.setComponent(CAN_FLY, true);
    entity.setComponent(CAN_BE_RIDDEN, false);
    entity.setComponent(SOME_BIRD_FUNCTIONALITY, new BirdFunction());
}

public void createPegasus(Entity entity){
    createHorse(entity);
    createBird(entity);
    entity.setComponent(CAN_BE_RIDDEN, true);
}

4

você pode ter uma hierarquia de interface e depois estender suas classes a partir de interfaces selecionadas:

public interface IAnimal {
}

public interface IBird implements IAnimal {
}

public  interface IHorse implements IAnimal {
}

public interface IPegasus implements IBird,IHorse{
}

e defina suas classes conforme necessário, estendendo uma interface específica:

public class Bird implements IBird {
}

public class Horse implements IHorse{
}

public class Pegasus implements IPegasus {
}

1
Ou ele pode simplesmente: Classe pública Pegasus estende Implementos Animais Cavalo, Pássaro
Batman

OP já está ciente desta solução que está procurando maneira alternativa de fazer isso
Yogesh

@Batman, é claro que ele pode, mas se ele quiser estender a hierarquia, ele precisará seguir essa abordagem #
21914

IBirde IHorsedeve implementar em IAnimalvez deAnimal
oliholz

@Yogesh, você está certo. Eu negligenciei o lugar onde ele afirma. Como "novato", o que devo fazer agora, excluir a resposta ou deixá-la lá ?, obrigado.
Richardtz

4

Ehm, sua classe pode ser a subclasse de apenas 1 outra, mas ainda assim, você pode ter quantas interfaces implementadas, conforme desejar.

Um Pegasus é de fato um cavalo (é um caso especial de cavalo), capaz de voar (que é a "habilidade" deste cavalo especial). Por outro lado, você pode dizer que o Pegasus é um pássaro, que pode andar e tem 4 patas - tudo depende de como é mais fácil escrever o código.

Como no seu caso, você pode dizer:

abstract class Animal {
   private Integer hp = 0; 
   public void eat() { 
      hp++; 
   }
}
interface AirCompatible { 
   public void fly(); 
}
class Bird extends Animal implements AirCompatible { 
   @Override
   public void fly() {  
       //Do something useful
   }
} 
class Horse extends Animal {
   @Override
   public void eat() { 
      hp+=2; 
   }

}
class Pegasus extends Horse implements AirCompatible {
   //now every time when your Pegasus eats, will receive +2 hp  
   @Override
   public void fly() {  
       //Do something useful
   }
}

3

As interfaces não simulam herança múltipla. Os criadores de Java consideraram a herança múltipla incorreta, portanto não existe tal coisa em Java.

Se você deseja combinar a funcionalidade de duas classes em uma composição de objeto de uso único. Ou seja,

public class Main {
    private Component1 component1 = new Component1();    
    private Component2 component2 = new Component2();
}

E se você deseja expor certos métodos, defina-os e deixe-os delegar a chamada ao controlador correspondente.

Aqui, as interfaces podem ser úteis - se Component1implementa interface Interface1e Component2implementa Interface2, você pode definir

class Main implements Interface1, Interface2

Para que você possa usar objetos de forma intercambiável onde o contexto permitir.

Então, no meu ponto de vista, você não pode entrar no problema dos diamantes.


Não está errado da mesma maneira que indicadores diretos de memória, tipos não assinados e sobrecarga de operadores não estão errados; simplesmente não é necessário concluir o trabalho. Java foi projetado como uma linguagem enxuta fácil de pegar. Não invente coisas e espere o melhor, este é um campo do conhecimento, não de suposições.
Gimby


3
  1. Defina interfaces para definir os recursos. Você pode definir várias interfaces para vários recursos. Esses recursos podem ser implementados por animais ou aves específicos .
  2. Usar herança para estabelecer relacionamentos entre classes, compartilhando dados / métodos não estáticos e não públicos.
  3. Use Decorator_pattern para adicionar recursos dinamicamente. Isso permitirá reduzir o número de classes e combinações de herança.

Dê uma olhada no exemplo abaixo para entender melhor

Quando usar o padrão do decorador?


2

Para reduzir a complexidade e simplificar o idioma, a herança múltipla não é suportada em java.

Considere um cenário em que A, B e C são três classes. A classe C herda as classes A e B. Se as classes A e B tiverem o mesmo método e você o chamar de objeto de classe filho, haverá ambiguidade para chamar o método da classe A ou B.

Como os erros em tempo de compilação são melhores que os erros em tempo de execução, o java renderiza o erro em tempo de compilação se você herdar 2 classes. Então, se você tem o mesmo método ou diferente, haverá um erro de tempo de compilação agora.

class A {  
    void msg() {
        System.out.println("From A");
    }  
}

class B {  
    void msg() {
        System.out.println("From B");
    }  
}

class C extends A,B { // suppose if this was possible
    public static void main(String[] args) {  
        C obj = new C();  
        obj.msg(); // which msg() method would be invoked?  
    }
} 

2

Para resolver o problema da herança múltipla em Java → a interface é usada

Notas do J2EE (JAVA principal) do Sr. KVR Página 51

Dia - 27

  1. As interfaces são basicamente usadas para desenvolver tipos de dados definidos pelo usuário.
  2. Com relação às interfaces, podemos alcançar o conceito de múltiplas heranças.
  3. Com interfaces, podemos alcançar o conceito de polimorfismo, ligação dinâmica e, portanto, podemos melhorar o desempenho de um programa JAVA em turnos de espaço na memória e tempo de execução.

Uma interface é uma construção que contém a coleção de métodos puramente indefinidos ou uma interface é uma coleção de métodos puramente abstratos.

[...]

Dia - 28:

Sintaxe-1 para reutilizar os recursos da (s) interface (s) na classe:

[abstract] class <clsname> implements <intf 1>,<intf 2>.........<intf n>
{
    variable declaration;
    method definition or declaration;
};

Na sintaxe acima, clsname representa o nome da classe que está herdando os recursos do número 'n' de interfaces. 'Implementa' é uma palavra-chave usada para herdar os recursos da (s) interface (s) para uma classe derivada.

[...]

Sintaxe-2 herdando 'n' número de interfaces para outra interface:

interface <intf 0 name> extends <intf 1>,<intf 2>.........<intf n>
{     
    variable declaration cum initialization;
    method declaration;
};

[...]

Sintaxe-3:

[abstract] class <derived class name> extends <base class name> implements <intf 1>,<intf 2>.........<intf n>
{
  variable declaration;
  method definition or declaration;
};
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.