Composição vs Herança
Parte da seção Programação Orientada a Objetos do Journey de Java da Coddy — lição 47 de 87.
A herança cria um relacionamento "é um": um Dog é um Animal. Mas e se uma classe precisar de funcionalidade de algo que ela não é? É aqui que entra a composição, estabelecendo, em vez disso, um relacionamento "tem um".
Com composição, uma classe contém instâncias de outras classes como campos em vez de estendê-las:
// Abordagem de herança: Car É UM Engine (não faz sentido)
class Car extends Engine { }
// Abordagem de composição: Car TEM UM Engine (faz sentido)
class Car {
private Engine engine;
public Car(Engine engine) {
this.engine = engine;
}
public void start() {
engine.ignite();
}
}A composição oferece várias vantagens. Você pode alterar o objeto composto em tempo de execução, como trocar motores.
Você também pode combinar comportamentos de múltiplas classes, algo que a herança não permite em Java. A classe composta expõe apenas os métodos que ela escolhe, proporcionando um melhor encapsulamento.
Uma diretriz prática: use herança quando houver um relacionamento genuíno "é-um" e a subclasse realmente representar uma versão especializada do pai.
Use composição quando você precisar usar a funcionalidade de outra classe sem ser desse tipo. Muitos desenvolvedores experientes seguem o princípio: "prefira a composição em vez da herança" porque isso leva a um código mais flexível e fácil de manter.
Desafio
FácilVamos construir um sistema de computador usando composição para demonstrar o relacionamento "tem-um" (has-a). Em vez de um Computer herdar de seus componentes (o que não faria sentido), seu computador irá conter as partes que ele precisa para funcionar.
Você organizará seu código em três arquivos:
Processor.java: Crie uma classe que represente uma CPU. Um Processor possui dois campos privados:brand(String) espeedGHz(double). Inclua um construtor para inicializar ambos os campos e métodos getter para cada um. Adicione um métodoprocess()que retorna uma String:[brand] processing at [speedGHz] GHzMemory.java: Crie uma classe que represente a memória RAM. Uma Memory possui dois campos privados:type(String, como "DDR4" ou "DDR5") esizeGB(int). Inclua um construtor, getters e um métodoload()que retorna:Loading [sizeGB]GB [type] memoryComputer.java: É aqui que a composição brilha! Sua classe Computer deve ter um Processor e uma Memory como campos privados — ela não os estende, ela os contém. Inclua um construtor que aceite ambos os componentes. Adicione um métodoboot()que retorna uma String de várias linhas mostrando o computador iniciando ao usar seus componentes:Adicione também um métodoBooting computer... [processor.process() result] [memory.load() result] System ready!getSpecs()que retorna:Specs: [processor brand] CPU, [memory sizeGB]GB [memory type]Main.java: Dê vida ao seu sistema composto! Você receberá quatro entradas: marca do processador (String), velocidade do processador (double), tipo de memória (String) e tamanho da memória (int).Crie um Processor e uma Memory com esses valores, depois componha-os em um Computer. Imprima o resultado da chamada de
boot(), depois imprima o resultado degetSpecs().
Você receberá quatro entradas em ordem: marca do processador, velocidade do processador (GHz), tipo de memória e tamanho da memória (GB).
Observe como o Computer delega o trabalho para seus componentes em vez de tentar ser eles — este é o poder da composição! O Computer pode usar qualquer Processor ou Memory que você fornecer, tornando o design flexível e realista.
Folha de consulta
A composição cria um relacionamento "tem-um" (has-a) onde uma classe contém instâncias de outras classes como campos, em vez de estendê-las.
Herança vs Composição:
// Herança: Car É UM Engine (não faz sentido)
class Car extends Engine { }
// Composição: Car TEM UM Engine (faz sentido)
class Car {
private Engine engine;
public Car(Engine engine) {
this.engine = engine;
}
public void start() {
engine.ignite();
}
}Vantagens da Composição:
- Objetos podem ser alterados em tempo de execução
- Pode combinar comportamentos de múltiplas classes
- Melhor encapsulamento - expõe apenas os métodos escolhidos
- Código mais flexível e fácil de manter
Quando Usar:
- Herança: Use quando houver um relacionamento genuíno "é-um" (is-a) e a subclasse for uma versão especializada da classe pai
- Composição: Use quando precisar da funcionalidade de outra classe sem ser desse tipo
Melhor Prática: "Prefira a composição em vez da herança"
Experimente você mesmo
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// Ler as entradas
String processorBrand = scanner.nextLine();
double processorSpeed = Double.parseDouble(scanner.nextLine());
String memoryType = scanner.nextLine();
int memorySize = Integer.parseInt(scanner.nextLine());
// TODO: Criar um Processor com a marca e velocidade
// TODO: Criar uma Memory com o tipo e tamanho
// TODO: Criar um Computer compondo o Processor e a Memory
// TODO: Imprimir o resultado de boot()
// TODO: Imprimir o resultado de getSpecs()
}
}
Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.
Todas as lições de Programação Orientada a Objetos
1Fundamentos de POO
Arquivos ExternosIntrodução à POOClasses vs ObjetosA Palavra-chave thisMétodosCampos (Atributos)Método ConstrutorSobrecarga de ConstrutorRecapitulação - Calculadora Simples4Herança
Herança Básica (extends)A Palavra-chave superSobrescrita de Método (@Override)Encadeamento de ConstrutoresA Classe ObjectHerança Simples e MultinívelPor que não há Herança Múltipla de ClassesRecapitulação - Hierarquia de Funcionários7Métodos Especiais e a Classe Object
Método toString()equals() e hashCode()Método clone()compareTo() e ComparableInterface ComparatorRecapitulação - Ordenação Personalizada2Modificadores de Acesso e Encapsulamento
Visão Geral dos Níveis de AcessoMétodos Getter e SetterOcultação de InformaçõesA Palavra-chave finalRecapitulação - Gerenciador de Conta Bancária5Polimorfismo
Fundamentos de Sobrecarga de MétodosSobrescrita de Métodos (Tempo de Execução)Upcasting e DowncastingO Operador instanceofClasses e Métodos AbstratosRecapitulação - Calculadora de Formas8Conceitos Avançados de OOP
Composição vs HerançaAgregação vs ComposiçãoClasses Internas, Aninhadas e AnônimasEnums e Métodos de EnumRecords (Java 16+)Classes Seladas (Java 17+)11Padrões de Projeto Parte 1
Introdução aos Padrões de ProjetoPadrão SingletonPadrão FactoryPadrão BuilderPadrão ObserverPadrão Strategy3Propriedades de Classe e Membros Estáticos
Variáveis de Instância vs EstáticasMétodos EstáticosBlocos EstáticosConstantes (static final)Recapitulação - Contador e Utilitário6Interfaces e Classes Abstratas
Introdução às InterfacesImplementando InterfacesImplementação de Múltiplas InterfacesDefault e Static em InterfacesClasses Abstratas vs InterfacesInterfaces FuncionaisRecapitulação - Sistema de Pagamento9Generics
Introdução a GenericsClasses GenéricasMétodos GenéricosParâmetros de Tipos DelimitadosWildcards (?, extends, super)Recapitulação - Container Genérico12Padrões de Projeto Parte 2
Padrão CommandPadrão AdapterPadrão DecoratorPadrão Template MethodPadrão StatePadrão CompositePadrão Iterator