Composição
Parte da seção Object Oriented Programming do Journey de Rust da Coddy — lição 55 de 61.
Muitas linguagens orientadas a objetos dependem fortemente da herança para compartilhar funcionalidades entre tipos. Rust adota uma abordagem diferente: composição. Em vez de herdar o comportamento de um pai, você constrói tipos complexos incluindo outros tipos como campos.
Considere modelar um carro. Em vez de criar uma classe base Vehicle e herdar dela, você compõe um Car a partir de componentes menores e focados:
struct Engine {
horsepower: u32,
}
struct Wheels {
count: u8,
diameter: f32,
}
struct Car {
engine: Engine,
wheels: Wheels,
brand: String,
}
O Car não herda de Engine ou Wheels—ele os tem. Esta relação "tem-um" é composição. Você acessa os componentes internos através de seus campos e pode chamar seus métodos diretamente:
impl Engine {
fn start(&self) {
println!("Engine with {} HP started!", self.horsepower);
}
}
impl Car {
fn start(&self) {
self.engine.start(); // Delega para o componente interno
}
}
Este padrão mantém cada struct focada em uma única responsabilidade. Precisa alterar como os motores funcionam? Modifique apenas a struct Engine. Quer reutilizar Wheels para uma motocicleta? Basta incluí-la em uma nova struct. A composição oferece flexibilidade sem o acoplamento rígido que a herança costuma criar.
Desafio
FácilVamos construir um sistema de computador usando composição! Em vez de criar uma única struct massiva, você comporá um Computer a partir de componentes menores e focados — um CPU e uma Memory. Cada componente saberá como se descrever, e o Computer delegará as tarefas para suas partes.
Você organizará seu código em três arquivos:
cpu.rs: Defina uma struct públicaCPUcom dois campos públicos:cores(u32) para o número de núcleos, espeed_ghz(f32) para a velocidade do clock. Adicione um métodospecsque retorna uma String descrevendo a CPU no formato"{cores}-core @ {speed_ghz}GHz".memory.rs: Defina uma struct públicaMemorycom um campo públicosize_gb(u32) para o tamanho da memória. Adicione um métodospecsque retorna uma String no formato"{size_gb}GB RAM".main.rs: Importe ambos os módulos e crie uma struct públicaComputerque possui um campocpudo tipoCPUe um campomemorydo tipoMemory. Implemente um métodosystem_infono Computer que delega para seus componentes e imprime a especificação completa do sistema. Use as entradas fornecidas para construir um Computer e exibir suas informações.
O método system_info deve imprimir:
System: {cpu_specs} | {memory_specs}Por exemplo, com as entradas 8, 3.5, e 16:
System: 8-core @ 3.5GHz | 16GB RAME com as entradas 4, 2.8, e 32:
System: 4-core @ 2.8GHz | 32GB RAMVocê receberá três entradas: núcleos da CPU (analise como u32), velocidade da CPU (analise como f32) e tamanho da memória (analise como u32).
Folha de consulta
Rust usa composição em vez de herança para compartilhar funcionalidade entre tipos. Você constrói tipos complexos incluindo outros tipos como campos, criando uma relação de "tem um" (has-a).
Composição Básica
Defina structs menores e focadas e componha-as em structs maiores:
struct Engine {
horsepower: u32,
}
struct Wheels {
count: u8,
diameter: f32,
}
struct Car {
engine: Engine,
wheels: Wheels,
brand: String,
}
Delegando para Componentes
Acesse componentes internos através de seus campos e chame seus métodos:
impl Engine {
fn start(&self) {
println!("Engine with {} HP started!", self.horsepower);
}
}
impl Car {
fn start(&self) {
self.engine.start(); // Delegate to the inner component
}
}
A composição mantém cada struct focada em uma única responsabilidade, proporcionando flexibilidade sem acoplamento forte.
Experimente você mesmo
mod cpu;
mod memory;
use cpu::CPU;
use memory::Memory;
// TODO: Defina uma struct Computer pública com dois campos públicos:
// - cpu: CPU
// - memory: Memory
// TODO: Implemente um método system_info em Computer que:
// - Delega para cpu.specs() e memory.specs()
// - Imprime: System: {cpu_specs} | {memory_specs}
fn main() {
let mut input = String::new();
std::io::stdin().read_line(&mut input).expect("Failed to read line");
let cores: u32 = input.trim().parse().expect("Invalid number");
input.clear();
std::io::stdin().read_line(&mut input).expect("Failed to read line");
let speed_ghz: f32 = input.trim().parse().expect("Invalid number");
input.clear();
std::io::stdin().read_line(&mut input).expect("Failed to read line");
let size_gb: u32 = input.trim().parse().expect("Invalid number");
// TODO: Crie instâncias de CPU e Memory usando as entradas processadas
// TODO: Crie uma instância de Computer com a CPU e a Memory
// TODO: Chame system_info() no computador para imprimir o resultado
}
Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.
Todas as lições de Object Oriented Programming
1Métodos e Comportamento
Introdução a Blocos de ImplementaçãoO Parâmetro SelfMétodos MutáveisFunções AssociadasMúltiplos Blocos de ImplementaçãoEncadeamento de MétodosRecapitulação - Ações de Retângulo4Projeto: Pet Virtual
Definindo o PetAlimentando o Pet7Traits Padrão
A Trait DebugA Trait DisplayClone e CopyTraits de IgualdadeRecapitulação - Ponto Imprimível10Projeto: Sistema de Documentos
A Trait DrawComponente de Texto2Encapsulamento e Módulos
Fundamentos de MódulosA Palavra-chave PublicCampos PrivadosGettersSettersRecapitulação - Cofre Seguro5Genéricos
Structs GenéricasMétodos GenéricosMúltiplos Tipos GenéricosFunções GenéricasRecapitulação - Ponto de Coordenada8Traits como Restrições
Sintaxe de Restrições de TraitRestrições MúltiplasA Cláusula WhereRetornando Tipos com TraitsRecapitulação - Impressora Genérica11Padrões de Projeto em Rust
Padrão NewtypeComposiçãoA Trait DropFrom e IntoRecapitulação - Mock de Smart Pointer3Enums Avançados
Enums com DadosMétodos em EnumsCorrespondência de Variantes de DadosO Enum Option RevisitadoRecapitulação - Enum Shape6Definição de Traits
O que é uma Trait?Implementando TraitsImplementações PadrãoSobrescrevendo PadrõesTraits com ParâmetrosRecapitulação - Media Player