Menu
Coddy logo textTech

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.

challenge icon

Desafio

Fácil

Vamos 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) e speedGHz (double). Inclua um construtor para inicializar ambos os campos e métodos getter para cada um. Adicione um método process() que retorna uma String: [brand] processing at [speedGHz] GHz
  • Memory.java: Crie uma classe que represente a memória RAM. Uma Memory possui dois campos privados: type (String, como "DDR4" ou "DDR5") e sizeGB (int). Inclua um construtor, getters e um método load() que retorna: Loading [sizeGB]GB [type] memory
  • Computer.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étodo boot() que retorna uma String de várias linhas mostrando o computador iniciando ao usar seus componentes:
    Booting computer...
    [processor.process() result]
    [memory.load() result]
    System ready!
    Adicione também um método 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 de getSpecs().

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()
    }
}
quiz iconTeste seus conhecimentos

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