Composição vs Herança

Composição vs Herança é uma das perguntas frequentemente feitas em entrevistas. Você também deve ter ouvido falar para usar Composição em vez de Herança.

Composição vs Herança

Ambas composição e herança são conceitos de programação orientada a objetos. Elas não estão vinculadas a nenhuma linguagem de programação específica, como Java. Antes de compararmos composição sobre herança programaticamente, vamos ter uma definição rápida delas.

Composição

Composição é a técnica de design em programação orientada a objetos para implementar o relacionamento tem-um entre objetos. Em Java, a composição é alcançada usando variáveis de instância de outros objetos. Por exemplo, uma pessoa que tem um trabalho é implementada da seguinte forma em programação orientada a objetos em Java.

package com.journaldev.composition;

public class Job {
// variáveis, métodos, etc.
}
package com.journaldev.composition;

public class Person {

    // composição tem um relacionamento
    private Job job;

    // variáveis, métodos, construtores, etc. orientado a objetos

Herança

Herança é a técnica de design na programação orientada a objetos para implementar a relação é-um entre objetos. A herança em Java é implementada usando a palavra-chave extends. Por exemplo, a relação Gato é um Animal na programação Java será implementada como abaixo.

package com.journaldev.inheritance;
 
public class Animal {
 // variáveis, métodos etc. 
}
package com.journaldev.inheritance;
 
public class Cat extends Animal{
}

Composição sobre Herança

Tanto a composição quanto a herança promovem a reutilização de código por meio de abordagens diferentes. Então, qual escolher? Como comparar composição vs herança. Você deve ter ouvido que na programação você deve favorecer a composição sobre a herança. Vamos ver algumas das razões que o ajudarão a escolher composição vs herança.

  1. A herança é fortemente acoplada, enquanto a composição é fracamente acoplada. Vamos supor que temos as classes abaixo com herança.

    package com.journaldev.java.examples;
    
    public class ClassA {
    
    	public void foo(){	
    	}
    }
    
    class ClassB extends ClassA{
    	public void bar(){
    		
    	}
    }
    

    Para simplicidade, temos tanto a superclasse quanto a subclasse em um único pacote. Mas geralmente estarão em bases de código separadas. Pode haver muitas classes estendendo a superclasse ClassA. Um exemplo muito comum dessa situação é estender a classe Exception. Agora, vamos dizer que a implementação da ClassA é alterada como abaixo, um novo método bar() é adicionado.

    package com.journaldev.java.examples;
    
    public class ClassA {
    
    	public void foo(){	
    	}
    	
    	public int bar(){
    		return 0;
    	}
    }
    

    Assim que começar a usar a nova implementação da ClassA, você receberá um erro de compilação na ClassB como O tipo de retorno é incompatível com ClassA.bar(). A solução seria alterar o método bar() da superclasse ou da subclasse para torná-los compatíveis. Se você tivesse usado Composição em vez de herança, nunca enfrentaria esse problema. Um exemplo simples da implementação da ClassB usando Composição pode ser o seguinte.

    class ClassB{
    	ClassA classA = new ClassA();
    	
    	public void bar(){
    		classA.foo();
    		classA.bar();
    	}
    }
    
  2. Não há controle de acesso na herança, enquanto o acesso pode ser restrito na composição. Expondo todos os métodos da superclasse para outras classes que têm acesso à subclasse. Portanto, se um novo método for introduzido ou se houver falhas de segurança na superclasse, a subclasse se torna vulnerável. Já na composição, escolhemos quais métodos utilizar, sendo mais seguro do que a herança. Por exemplo, podemos fornecer a exposição do método ClassA foo() para outras classes usando o código abaixo na ClassB.

    class ClassB {
    	
    	ClassA classA = new ClassA();
    	
    	public void foo(){
    		classA.foo();
    	}
    	
    	public void bar(){	
    	}
    	
    }
    

    Essa é uma das principais vantagens da composição sobre a herança.

  3. A composição oferece flexibilidade na invocação de métodos, o que é útil em cenários com várias subclasses. Por exemplo, suponha que tenhamos o seguinte cenário de herança.

    abstract class Abs {
    	abstract void foo();
    }
    
    public class ClassA extends Abs{
    
    	public void foo(){	
    	}
    	
    }
    
    class ClassB extends Abs{
    		
    	public void foo(){
    	}
    	
    }
    
    class Test {
    	
    	ClassA a = new ClassA();
    	ClassB b = new ClassB();
    
    	public void test(){
    		a.foo();
    		b.foo();
    	}
    }
    

    E se houver mais subclasses, a composição tornará nosso código feio, tendo uma instância para cada subclasse? Não, podemos reescrever a classe Teste da seguinte maneira.

    class Teste {
    	Abs obj = null;
    	
    	Teste(Abs o){
    		this.obj = o;
    	}
    	
    	public void foo(){
    		this.obj.foo();
    	}
    
    }
    

    Isso lhe dará a flexibilidade de usar qualquer subclasse com base no objeto usado no construtor.

  4. Mais um benefício da composição sobre a herança é o escopo de teste. O teste de unidade é fácil na composição porque sabemos quais métodos estamos usando de outra classe. Podemos simulá-lo para teste, enquanto na herança dependemos muito da superclasse e não sabemos quais métodos da superclasse serão usados. Portanto, teremos que testar todos os métodos da superclasse. Isso é trabalho extra e precisamos fazer isso desnecessariamente por causa da herança.

Isso é tudo para composição versus herança. Você tem motivos suficientes para escolher a composição em vez da herança. Use herança apenas quando tiver certeza de que a superclasse não será alterada, caso contrário, opte pela composição.

Source:
https://www.digitalocean.com/community/tutorials/composition-vs-inheritance