Herança Multipla

Mais só porque é possivel não quer dizer que seja a melhor maneira. da pra fazer qualquer laço de repetição em pascal usando apenas if e goto mais nem por isso retiraram os fors e whiles.

Isso cai no que eu tinha falado, parece que a linguagem presume que o programador é idiota. e que para evitar dele cometer erros tiram recursos que possam ser uteis mas que consideram dificeis. :wink:

Esse exemplo do ativo não é o melhor para demostrar a necessidade de HM porque começa com um erro de modelagem. Um Transporte não é um ativo (Violação do É-UM). Aliás , pode ser um Passivo :lol:

VC está querendo reaproveitar codigo…

O que eu quero dizer é que “Ativo” é um papel que Transporte desempenha num contexto (Contábil). Isso não é modelável com “É-UM” (É o mesmo problema de pessoa fisica e juridica) Teria que ser assim :

Transporte t = ...
Activo act = Activo.ativoPara(t);

Eu entendi o que vc quiz dizer, mas duvido que usar HM seja sinônimo de boa modelagem.

Em nenhum momento eu disse que Transporte é um ativo.

public class Transporte
{
}

Helicoptero, Carro e Yate são Ativos.

Em nenhum momento eu disse que Transporte é um ativo.

public class Transporte
{
}

Helicoptero, Carro e Yate são Ativos.[/quote]

Ok, mas o argumento se mantem. Só trocar “transporte” por qualquer um dos transportes reais.

Motivo de Java não ter herança múltipla é por causa do efeito Diamante que pode acontecer no Design.

AndrewAguiar mostrou um exemplo bastante interessante sobre herança múltipla. Mas eu poderia mostrar esse mesmo exemplo, sem utilizar herança múltipla e, ao mesmo tempo, sem replicação de código. Veja:

A interface Ativo e a classe Transporte seriam as mesmas.

public interface Ativo
{
     double getValor();

     int getTempoDepreciacao();
}

public class Transporte
{
	// Varios metodos.
}

Agora eu teria a classe AtivoImpl, que implementa Ativo:

public class AtivoImpl implements Ativo
{
	private double valor;
	private int tempoDepreciacao;

	public double getValor(){
		return valor;
	}
	
	public int getTempoDepreciacao(){
		return tempoDepreciacao;
	}
}

E aí, as classes que herdam de Transporte (não todas) teriam uma instância de Ativo:

public class Helicoptero extends Transporte
{
	// Metodos proprios

	private Ativo ativo = new AtivoImpl();

	public Ativo getAtivo(){
		return ativo;
	}
}
public class Yate extends Transporte
{
	// Metodos proprios

	private Ativo ativo = new AtivoImpl();

	public Ativo getAtivo(){
		return ativo;
	}
}

public class CarroParticular extends Transporte
{
	// Metodos proprios

	private Ativo ativo = new AtivoImpl();

	public Ativo getAtivo(){
		return ativo;
	}
}

E aí, sempre que o cliente precisar de saber algo do transporte como ativo, basta fazer:

CarroParticular cp = new CarroParticular();
// um monte de regras...
Ativo ativoCarroParticular = cp.getAtivo();
double valor = ativoCarroParticular.getValor();

Tudo bem, não é tão bonito se tivesse herança múltipla, mas ela resolve o problema de não se repetir código, ao mesmo tempo fazendo uso apenas de herança simples. E isso pode ser feito em qualquer situação onde a modelagem pede a herança múltipla, basta pegar o “elo mais fraco”, no nosso caso a inteface Ativo, e transformá-la em um relacionamento TEM-UM.

Um outro problema da herança múltipla é o efeito diamante que, se não for bem pensado, pode gerar um pepino bem grande pro desenvolvedor. Imaginemos que Java possua herança múltipla, e que tivéssemos as classes Tranporte e Ativo estendendo diretamente Object. Então, estenderiamos tanto Transporte e Ativo para a criação da classe Yate, porém sem o método equals.

Bom, chega um certo momento que uma referência Object possua uma instancia Yate, assim:

Object obj = yate;

Aí, numa outra parte do código, quero chamar equals()

obj.equals(anotherObj);

Pergunta: o que que o interpretador java irá fazer? Não pode chamar equals de Yate, pq não tem. Tanto Transporte quanto Ativo possuem o mesmo nível de hierarquia, e não daria pra chamar um antes do outro ou escolher um deles pois não existe nada codificado dizendo isso. E não dá pra seguir tipo uma “convenção sobre configuração” dizendo que a primeira classe após a palavra extends é a que vale, porque geraria muita confusão pros novatos que veriam um programa não-responsivo.

Bom, poderíamos criar o método equals na classe Yate, então! Mas se precisarmos chamar o super, quem seria a referência? O C++ tem o operador de escopo (::slight_smile: onde basta escolher qual superclasse se quer utilizar, mas isso só acontece porque C++ possui um modelo de gerenciamento memória bastante diferente do Java, o qual dá pra fazer isso. Em Java, nem pensar.

Uma outra situação, e se Transporte e Ativo não estendessem Object, mas ObjectPlus, cuja implementação é essa?

public class ObjectPlus {
    
    int quantidade;

    public int getQuantidade() {
        return quantidade;
    }

    public void setQuantidade(int quantidade) {
        this.quantidade = quantidade;
    }
    
}

Imagine: Transporte tem a sua variável quantidade, Ativo também, e Yate? Sendo herdeiro de duas classes, não teria duas variáveis “quantidade”? E quando eu chamo:

ObjectPlus objPlus = yate;
objPlus.setQuantidade(3);

De que implementação eu estou considerando? Transporte ou Ativo?

Por essas coisas, é que é bem melhor usar herança simples, não dá tanta dor de cabeça. Até mesmo quando eu programava em C++, só usava herança simples. Na única vez que tentei, deu um monte de pau, e como as mensagens de erro não são lá muito claras, resolvi nem seguir adiante.

HM é confusa mas C++ e Python tem :wink:

Todos os problemas citados com herança múltipla são resolvidos em Eiffel, C++, Python ou na teoria de tipos. java não tem herança múltipla por design. De qualquer forma as pessoas ainda não conseguiram depois de 40 anos entender herança simples, imagine múltipla. Eu sou cada vez mais favorável à linguagens de escopo reduzido e DSLs.

Eu sinceramente acho que o Java faz bem em evitar recursos que podem causar dores de cabeças, imaginando que o programador é descuidado.

A história tem mostrado que isso é o que ocorre. Veja o caso do C++. O programador é obrigado a se lembrar de dar delete nos seus ponteiros. E isso geralmente não acontece. Os programadores de C++, usando os recursos de templates, inventaram os Smart Pointers. Um recurso específico para se proteger de si mesmos, do próprio esquecimento (ok, há outras razões, mas essa é a mais alegada).

Além disso, se você pegar livros como Effective C++ ou C++ for Game Programmers, vai ver que eles são unânimes em dizer para você não usar a herança múltipla, exceto no caso de Abstract Base Classes. E o que são Abstract Base Classes? Um conceito muito próximo das interfaces do java. Classes 100% abstrata, sem propriedades e sem métodos.

Finalmente, o tempo tem mostrado que hierarquias de classes curtas, com mais acoplamento do que herança, são mais sólidas que grandes cadeias de herança.

Eu sou um programador C++ a bastante tempo. Adoro a linguagem. Mas, o C++ exige que você seja um programador atento o tempo todo. Chega a ser uma certa paranóia. E isso é ruim, muito ruim. Programar em Java é um prazer, porque diversos fatores de stress foram removidos. E isso é bom, muito bom.

[quote=ViniGodoy]Eu sinceramente acho que o Java faz bem em evitar recursos que podem causar dores de cabeças, imaginando que o programador é descuidado.
[/quote]

Eu concordo e acho pouco. As nvoas linguagens devem limitar o programador normal muito mais e focá-lo no negócio.

O problema é que C++ e Java são system languages que acabam virando kitchen sink languages, ou sacos de funcionalidades.

Ahm… não.

Interfaces são contratos. Uma interface não cria o relacionamento que existe entre uma classe e sua superclasse como obediência á Liskov Substitution Principle, uma classe abstrata sim. interfaces são recursos para garantir que classes possuam uma “interface” (uma API) comum.

Acho qeu você quis dizer composição e não acomplamento, certo? De qualquer modo, herança múltipla não implica emg randes hierarquias ou vice-versa.

[quote=Leonardo3001 ]AndrewAguiar mostrou um exemplo bastante interessante sobre herança múltipla. Mas eu poderia mostrar esse mesmo exemplo, sem utilizar herança múltipla e, ao mesmo tempo, sem replicação de código. Veja:

A interface Ativo e a classe Transporte seriam as mesmas.
…[/quote]

Sem duplicação.

Voce esta colocando uma instancia de Ativo em cada classe e ainda por cima não é correto dizer que Um Helicoptero tem um ativo, e sim que é um ativo da empresa…

com HM voce só precisaria herdar de AtivoImp e pronto…

e ainda por cima deste modo voce não consegue se referir ao Helicoptero, Yate e Carro como sendo um ativo…

Helicoptero, Carro e Yate serão SEMPRE Ativos? Tem certeza disso? Pense um pouco.

Há sim um erro de modelagem no exemplo que você passou.

Em muitos casos é interessante evitar até mesmo a herança simples, que dirá herança múltipla. Não consegui contemplar até hoje um cenário real em que a herança múltipla fosse realmente necessária e não pudesse ser implementada de uma melhor forma utilizando interfaces ou strategy ou quaisquer outras soluções cabíveis ao contexto.

Definitivamente Helicoptero não é um ativo, ele pode ser um ativo em um dado momento, e só.

Isso me lembra uma frase que vi há algum tempo… não me lembro o autor:

Dependendo da modelagem sim.

Não ha erro algum na modelagem, e o fato de ele poder ser um ativo em determinada ocasião torna ele um ativo.
Se for assim a classe String não precisaria implementar CharSequence pois nem sempre voce esta lidando com uma CharSequence. na maioria das vezes voce so quer manipular uma String.

Sim voce consegue simular HM com interfaces mais estas são meras gambis, o fato é que voce tera que implementar comportamentos comuns para varias classes sendo que poderia ser implementado na classe.

Eu pensei em responder algo assim. Mas não concordo.

O compilador C++ fez uma solução simplista para o problema, mas o Java poderia partir para algo mais inteligente.

Para quem não sabe do problema, conside as classes:
Veiculo
Naval extends Veiculo
Terrestre extends Veiculo
Anfibio extends Terrestre, Naval

A classe veículo é pai de Anfíbio 2 vezes, uma delas através de Naval e outra através de Terrestre. No C++ isso é um problema, pois o compilador gerará efetivamente duas cópias da classe veículo.

O que acho que deveria acontecer, e é o que parece lógico, é que veículo não se duplicasse. Afinal, veículo é apenas uma classe.

Outro problema da herança múltipla, e aí sim, é um problema sério é o conflito de nomes. Ainda naquele diagrama. Considere que tanto a classe Naval quanto Terrestre tem um método com o mesmo nome, digamos, getMotor(). Entretanto, esse método é radicalmente diferente nas duas implementações. A grande questão é… o que fazer com ele?

Com interfaces, isso não é um problema, pois interfaces não tem implementações. Uma coisa que o java poderia fazer é impedir herança quando isso acontecesse. Ou limitasse isso para quando as classes implementassem uma interface comum e ainda obrigasse o programador a implementar alguma coisa no método conflitante. Acharam complicado?

Agora, o que fazer com campos protected conflitantes? Shadowing de um deles? Eliminar um dos campos? Impedir?

Em resumo, herança múltipla é um recurso complexo e de benefício muito questionável. Geralmente complica o design e quase sempre pode ser evitado. Então, para que ter isso na linguagem? Para agradar 10% dos programadores? Mas será que isso é justificativa para complicar a vida dos outros 90%?

Eu pensei em responder algo assim. Mas não concordo.

O compilador C++ fez uma solução simplista para o problema, mas o Java poderia partir para algo mais inteligente.

Para quem não sabe do problema, conside as classes:
Veiculo
Naval extends Veiculo
Terrestre extends Veiculo
Anfibio extends Terrestre, Naval

A classe veículo é pai de Anfíbio 2 vezes, uma delas através de Naval e outra através de Terrestre. No C++ isso é um problema, pois o compilador gerará efetivamente duas cópias da classe veículo.

O que acho que deveria acontecer, e é o que parece lógico, é que veículo não se duplicasse. Afinal, veículo é apenas uma classe.

Outro problema da herança múltipla, e aí sim, é um problema sério é o conflito de nomes. Ainda naquele diagrama. Considere que tanto a classe Naval quanto Terrestre tem um método com o mesmo nome, digamos, getMotor(). Entretanto, esse método é radicalmente diferente nas duas implementações. A grande questão é… o que fazer com ele?

Com interfaces, isso não é um problema, pois interfaces não tem implementações. Uma coisa que o java poderia fazer é impedir herança quando isso acontecesse. Ou limitasse isso para quando as classes implementassem uma interface comum e ainda obrigasse o programador a implementar alguma coisa no método conflitante. Acharam complicado?

Agora, o que fazer com campos protected conflitantes? Shadowing de um deles? Eliminar um dos campos? Impedir?

Em resumo, herança múltipla é um recurso complexo e de benefício muito questionável. Geralmente complica o design e quase sempre pode ser evitado. Então, para que ter isso na linguagem? Para agradar 10% dos programadores? Mas será que isso é justificativa para complicar a vida dos outros 90%?

[quote=pcalcado]Ahm… não.

Interfaces são contratos. Uma interface não cria o relacionamento que existe entre uma classe e sua superclasse como obediência á Liskov Substitution Principle, uma classe abstrata sim. interfaces são recursos para garantir que classes possuam uma “interface” (uma API) comum.[/quote]

Sim… sim… aqui estou usando o termo “Abstract Base Class” (ABCs) do mundo C++. Não confundir simplesmente com “Classe abstrata” do java. Eles são bem enfáticos em dizer que devem só conter métodos virtuais puros (ou seja, sem implementação), para que exprimam somente a funcionalidade exprimida pela ABC (em resumo, um contrato). Além do mais, quando se trata de uma herança múltipla com duas ABCs puras, o C++ não recai no problema do Diamond-of-Death. Existe código no compilador específico para isso. Aliás, sobre isso, recomendo a entrevista com o próprio autor do Effective C++:
http://www.artima.com/intv/abcs.html

Mas concordo, não é um conceito idêntico. E sempre há o risco de alguém começar a colocar código na ABC, quebrando todo conceito da arquitetura.

É, foi o que eu quis dizer. :slight_smile:

Realmente, herança múltipla não implica em grandes hierarquias. Mas as favorece. Especialmente o que se faz com “mixing classes”, que são classes de utilidade pequena, desenhadas justamente para se misturarem a outras. E as mixing classes estão sujeitas a todos os problemas da herança múltipla, inclusive os conflitos de nomes que citei ali em cima.

Talvez eu não tenha sido claro, mas o que eu quis dizer é que ele pode fazer parte do ativo num determinado momento, mas isso não o torna um ativo. O ativo possui um helicóptero, o relacionamento TEM UM é mais correto, mais coerente. Continuo achando que há erro na modelagem.

Ou a idéia é usar herança simplesmente para digitar menos código? Essa não é a melhor forma de se usar herança.

Quanto a comportamentos comuns você pode separá-los usando strategy.

Ah… e o exemplo da interface CharSequence foi infeliz, uma String não é nada mais que uma sequência de caracteres, então pode ser tratado dessa forma sem problemas.

Duvida…e na hora que o Helicoptero não tiver mais serventia e virar um Passivo o que fazer?

]['s