Composição vs Herança

Composição vs Herança

A Composição vs Herança é uma das perguntas frequentemente feitas em entrevistas. Você também deve ter ouvido falar em usar Composição em vez de Herança. Ambos a composição e a herança são conceitos de programação orientada a objetos. Eles não estão vinculados a nenhuma linguagem de programação específica, como o Java. Antes de compararmos a composição com a herança programaticamente, vamos ter uma rápida definição delas.

Composição

A Composição é a técnica de design em programação orientada a objetos para implementar a relação tem-um entre objetos. A composição em Java é alcançada usando variáveis de instância de outros objetos. Por exemplo, uma pessoa que tem um emprego é 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 {

    // relação tem-um da composição 
    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 o relacionamento é-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 versus 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 ajudarão você a escolher composição versus herança.

  1. A herança é fortemente acoplada, enquanto a composição é fracamente acoplada. Vamos supor que temos as seguintes classes 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 na maioria das vezes, elas 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 foi alterada da seguinte forma, um novo método bar() foi adicionado.

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

    Ao começar a usar a nova implementação de ClassA, você receberá um erro de compilação em ClassB, indicando 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, você nunca enfrentaria esse problema. Um exemplo simples de 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. Exporíamos todos os métodos da superclasse para as outras classes que têm acesso à subclasse. Portanto, se um novo método for introduzido ou houver brechas de segurança na superclasse, a subclasse se tornará vulnerável. Como na composição escolhemos quais métodos usar, é mais seguro do que a herança. Por exemplo, podemos fornecer a exposição do método foo() da ClassA para outras classes usando o código abaixo na ClassB.

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

    Este é um dos principais benefícios da composição sobre a herança.

  3. A composição fornece flexibilidade na invocação de métodos, o que é útil em cenários com várias subclasses. Por exemplo, digamos 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 deixará nosso código feio ao ter uma instância para cada subclasse? Não, podemos reescrever a classe Teste da seguinte maneira.

    class Test {
    	Abs obj = null;
    	
    	Test1(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. Outra vantagem da composição sobre a herança é o escopo de teste. Os testes unitários são fáceis na composição porque sabemos quais métodos estamos utilizando de outra classe. Podemos simular isso para teste, enquanto na herança dependemos muito da superclasse e não sabemos quais métodos da superclasse serão utilizados. Portanto, teríamos que testar todos os métodos da superclasse. Isso é um trabalho extra e precisamos fazê-lo 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 a 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