Composição vs Herança
Parte da seção Programação Orientada a Objetos do Journey de C++ da Coddy — lição 76 de 104.
Ao projetar relacionamentos entre classes, você tem duas opções principais: herança ("é um") e composição ("tem um"). Escolher a abordagem correta impacta significativamente a flexibilidade e a manutenibilidade do seu código.
Herança cria um acoplamento forte entre classes. Um Car que herda de Vehicle está permanentemente vinculado a esse relacionamento:
class Vehicle {
public:
virtual void start() { std::cout << "Starting vehicle\n"; }
};
class Car : public Vehicle {
public:
void start() override { std::cout << "Starting car\n"; }
};Composição incorpora objetos como membros, criando um relacionamento mais flexível. Um Car tem um Engine em vez de ser um motor:
class Engine {
public:
void start() { std::cout << "Engine running\n"; }
};
class Car {
private:
Engine engine; // Car TEM um Motor
public:
void start() { engine.start(); }
};A principal vantagem da composição é a flexibilidade. Você pode facilmente trocar componentes, alterar o comportamento em tempo de execução ou combinar múltiplas capacidades sem as restrições de uma hierarquia de classes rígida. O design moderno de C++ favorece a composição em vez da herança na maioria dos casos.
Use herança quando houver um relacionamento genuíno "é um" (is-a) e você precisar de polimorfismo. Use composição quando quiser reutilizar funcionalidades ou quando o relacionamento for melhor descrito como "tem um" (has-a) ou "usa um" (uses-a).
Desafio
FácilVamos construir um simulador de sistema de computador que demonstra quando usar composição em vez de herança. Você modelará um computador que possui componentes (composição) em vez de ser um componente, mostrando como a composição oferece flexibilidade para trocar peças em tempo de execução.
Você organizará seu código em três arquivos:
Components.h: Defina os componentes individuais que um computador pode conter.Crie uma classe
CPUcom umstd::string modeleint coresprivados. Forneça um construtor que receba ambos os valores, um métodoprocess()que imprimaCPU [model] processing with [cores] cores, e um métodogetModel().Crie uma classe
Memorycom umint sizeGBprivado. Forneça um construtor, um métodoload()que imprimaLoading data into [sizeGB]GB RAM, e um métodogetSize().Crie uma classe
Storagecom umstd::string typeprivado (como "SSD" ou "HDD") eint capacityGB. Forneça um construtor, um métodoread()que imprimaReading from [capacityGB]GB [type], e métodos getter para ambos os campos.Computer.h: Defina uma classeComputerque use composição para combinar componentes.Seu
Computerdeve ter objetos membros privados: umaCPU, umaMemorye umStorage. Use a lista de inicialização do construtor para inicializar esses componentes a partir dos parâmetros.Implemente estes métodos:
boot()— imprimeBooting computer..., depois chamaload()na memória,read()no armazenamento eprocess()na CPU, cada um em linhas separadasspecs()— imprime as especificações do computador neste formato:Computer Specs: - CPU: [model] ([cores] cores) - RAM: [size]GB - Storage: [capacity]GB [type]
main.cpp: Leia seis entradas (cada uma em uma linha separada):- Nome do modelo da CPU (string)
- Número de núcleos da CPU (inteiro)
- Tamanho da memória em GB (inteiro)
- Tipo de armazenamento (string, por exemplo, "SSD")
- Capacidade de armazenamento em GB (inteiro)
- Um comando: "boot" ou "specs"
Crie os objetos componentes e, em seguida, crie um
Computerusando composição. Com base no comando, chameboot()ouspecs().
Por exemplo, com as entradas Intel i7, 8, 16, SSD, 512 e boot:
Booting computer...
Loading data into 16GB RAM
Reading from 512GB SSD
CPU Intel i7 processing with 8 coresCom as entradas AMD Ryzen, 6, 32, HDD, 1000 e specs:
Computer Specs:
- CPU: AMD Ryzen (6 cores)
- RAM: 32GB
- Storage: 1000GB HDDObserve como a classe Computer não herda de nenhum componente — ela os contém. Esse relacionamento "tem-um" significa que você poderia facilmente criar computadores com diferentes combinações de componentes, ou até mesmo trocar componentes mais tarde se adicionasse métodos setter. Essa flexibilidade é a principal vantagem da composição sobre a herança.
Folha de consulta
Ao projetar relacionamentos entre classes, escolha entre herança ("é-um") e composição ("tem-um").
A Herança cria um acoplamento forte entre as classes:
class Vehicle {
public:
virtual void start() { std::cout << "Starting vehicle\n"; }
};
class Car : public Vehicle {
public:
void start() override { std::cout << "Starting car\n"; }
};A Composição incorpora objetos como membros, criando relacionamentos flexíveis:
class Engine {
public:
void start() { std::cout << "Engine running\n"; }
};
class Car {
private:
Engine engine; // Car TEM um Engine
public:
void start() { engine.start(); }
};Quando usar cada uma:
- Use herança para relacionamentos genuínos do tipo "é-um" quando você precisar de polimorfismo
- Use composição para reutilizar funcionalidade ou para relacionamentos do tipo "tem-um"/"usa-um"
- O design moderno de C++ favorece a composição por sua flexibilidade — você pode trocar componentes, alterar o comportamento em tempo de execução e combinar recursos sem hierarquias rígidas
Experimente você mesmo
#include <iostream>
#include <string>
#include "Computer.h"
using namespace std;
int main() {
// Ler as entradas
string cpuModel;
getline(cin, cpuModel); // Nome do modelo da CPU (pode conter espaços)
int cpuCores;
cin >> cpuCores; // Número de núcleos da CPU
int memorySize;
cin >> memorySize; // Tamanho da memória em GB
string storageType;
cin >> storageType; // Tipo de armazenamento (ex: "SSD")
int storageCapacity;
cin >> storageCapacity; // Capacidade de armazenamento em GB
string command;
cin >> command; // Comando: "boot" ou "specs"
// TODO: Criar objetos de componentes (CPU, Memory, Storage)
// TODO: Criar um objeto Computer usando composição
// TODO: Com base no comando, chamar boot() ou specs()
return 0;
}
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 OOP
Arquivos ExternosBuild e Compilação em C++Arquivos Header e SourceNamespaces e EscopoIntrodução a OOP em C++Classes vs ObjetosO Ponteiro 'this'Métodos (Funções Membro)Atributos (Membros de Dados)Básico de Ctors e DtorsRecapitulação - Calculadora Simples4Propriedades de Classe
Membros de Instância vs EstáticosGetters e SettersFunções de Membro ConstPalavra-chave MutableMétodos e Variáveis EstáticosFunções e Classes FriendRecapitulação - Gerenciador de Conta Bancária7Herança
Herança BásicaNíveis de Acesso na HerançaOrdem de Chamada de Ctor e DtorSobrescrita de MétodosFunções Virtuais e VTableHerança MúltiplaHerança VirtualRecapitulação - Hierarquia de Funcionários2Gerenciamento de Memória
Memória Stack vs HeapPonteiros e ReferênciasMemória Dinâmica (new/delete)Smart Pointers em C++RAII em C++Recapitulação - Gerenciador de Array Dinâmico5Encapsulamento
Especificadores de Acesso em C++Especificadores de Acesso em DetalhesOcultação de InformaçãoStruct vs ClassClasses Aninhadas e InternasRecapitulação - Sistema de Registros de Alunos8Polimorfismo
Polimorfismo de Compilação vs Tempo de ExecuçãoSobrecarga de FunçõesFunções Virtuais RevisitadasFunções Virtuais PurasClasses AbstratasDesign de Interfaces em C++Dynamic Casting e RTTIRecapitulação - Calculadora de Formas11Conceitos Avançados de POO
Composição vs HerançaMixins via CRTPPimpl IdiomType ErasureEnum Classes e Tipagem ForteTratamento de Exceções em POOHierarquias de Exceções Personalizadas3Construtores e Destrutores
Construtor PadrãoConstrutor ParametrizadoConstrutor de CópiaConstrutor de MovimentaçãoListas de Inicialização do ConstrutorConstrutores DelegadosMergulho Profundo em DestrutoresRegra dos Três / Cinco / ZeroRecapitulação - Classe String6Sobrecarga de Operadores
Introdução à Sobrecarga de OperadoresSobrecarga de Operadores AritméticosSobrecarga de Operadores de ComparaçãoOperadores de StreamSobrecarga do Operador de AtribuiçãoSobrecarga dos Operadores [] e ()Operadores de Conversão de TipoRecapitulação - Classe Matrix9Templates
Templates de FunçãoTemplates de ClasseEspecialização de TemplatesTemplates VariádicosFundamentos de SFINAE e Type TraitsRecapitulação - Container Genérico