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 certa impacta significativamente a flexibilidade e a manutenibilidade do seu código.
Herança cria um forte acoplamento entre classes. Um Car que herda de Vehicle fica 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 Engine
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 vários recursos sem as limitações de uma hierarquia rígida de classes. O design moderno de C++ favorece a composição em vez da herança na maioria dos casos.
Use herança quando houver uma relação genuína de "is-a" e você precisar de polimorfismo. Use composição quando quiser reutilizar funcionalidades ou quando a relação for melhor descrita como "has-a" ou "uses-a".
Desafio
FácilVamos criar um simulador de sistema de computador que demonstra quando usar composição em vez de herança. Você modelará um computador que tem 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 modelprivado e umint cores. Forneça um construtor que receba ambos os valores, um métodoprocess()que exibaCPU [model] processing with [cores] corese um métodogetModel().Crie uma classe
Memorycom umint sizeGBprivado. Forneça um construtor, um métodoload()que exibaLoading data into [sizeGB]GB RAMe um métodogetSize().Crie uma classe
Storagecom umstd::string typeprivado (como "SSD" ou "HDD") e umint capacityGB. Forneça um construtor, um métodoread()que exibaReading from [capacityGB]GB [type]e métodos getter para ambos os campos.Computer.h: Defina uma classeComputerque use composição para combinar componentes.Sua classe
Computerdeve ter objetos-membro privados: umCPU, umaMemorye umStorage. Use a lista de inicialização do construtor para inicializar esses componentes a partir dos parâmetros.Implemente estes métodos:
boot(): exibeBooting computer...e, em seguida, chamaload()na memória,read()no armazenamento eprocess()na CPU, cada um em uma linha separadaspecs(): exibe 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 do armazenamento em GB (inteiro)
- Um comando: "boot" ou "specs"
Crie os objetos dos 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 de "tem um" significa que você poderia criar facilmente computadores com diferentes combinações de componentes ou até mesmo trocar componentes posteriormente se adicionasse métodos setter. Essa flexibilidade é a principal vantagem da composição sobre a herança.
Experimente você mesmo
#include <iostream>
#include <string>
#include "Computer.h"
using namespace std;
int main() {
// Ler 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, chame 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 POO
Arquivos externosBuild e compilação em C++Arquivos de cabeçalho e arquivos-fonteNamespaces e escopoIntrodução à POO em C++Classes vs. objetosO ponteiro 'this'Métodos (funções membro)Atributos (membros de dados)Noções básicas de construtores e destruidoresRevisão - calculadora simples4Propriedades de Classe
Membros de Instância vs. EstáticosGetters e SettersFunções Membro ConstPalavra-chave MutableMétodos e Variáveis EstáticosFunções e Classes FriendRevisão - Gerenciador de Contas Bancárias7Herança
Herança básicaNíveis de acesso da herançaOrdem de chamada de construtor e destrutorSobrescrita de métodosFunções virtuais e VTableHerança múltiplaHerança virtualRevisão - Hierarquia de funcionários10Visão geral da STL
Visão geral e filosofia da STLContêineres da STLIteradoresAlgoritmos da STLFunctors e expressões lambdaRecapitulação - Frequência de palavras13Padrões de Projeto — Parte 1
Introdução aos Padrões de ProjetoPadrão SingletonFactory e Abstract FactoryPadrão BuilderPadrão ObserverPadrão Strategy2Gerenciamento de Memória
Memória Stack vs. HeapPonteiros e ReferênciasMemória Dinâmica (new/delete)Ponteiros Inteligentes em C++RAII em C++Revisão - Gerenciador de Array Dinâmico5Encapsulamento
Especificadores de acesso em C++Especificadores de acesso em profundidadeOcultação de informaçõesStruct vs ClasseClasses aninhadas e internasRecapitulação - Sistema de registros de alunos8Polimorfismo
Polimorfismo em compilação vs. execuçãoSobrecarga de funçõesFunções virtuais revisadasFunções virtuais purasClasses abstratasDesign de interfaces em C++Conversão dinâmica e RTTIRecapitulação — Calculadora de formas11Conceitos Avançados de OOP
Composição vs. HerançaMixins via CRTPIdioma PimplApagamento de TiposClasses Enum e Tipagem ForteTratamento de Exceções em OOPHierarquias de Exceções Personalizadas14Padrões de Projeto — Parte 2
Padrão CommandPadrão AdapterPadrão DecoratorPadrão Template MethodPadrão StatePadrão CompositeRAII como Padrão3Construtores e Destrutores
Construtor PadrãoConstrutor ParametrizadoConstrutor de CópiaConstrutor de MovimentoListas de Inicialização do ConstrutorConstrutores DelegantesAprofundamento em DestrutoresRegra dos Três / Cinco / ZeroRevisão - Classe String6Sobrecarga de Operadores
Introdução à Sobrecarga de OperadoresSobrecarga de Operadores AritméticosSobrecarga de Operadores de ComparaçãoOperadores de FluxoSobrecarga do Operador de AtribuiçãoSobrecarga dos Operadores [] e ()Operadores de Conversão de TipoRecapitulação - Classe Matrix9Templates
Templates de FunçõesTemplates de ClassesEspecialização de TemplatesTemplates VariádicosNoções Básicas de SFINAE e Traits de TiposRecapitulação - Contêiner GenéricoPratique por conta própria: Compilador de C++ online