Composição sobre Herança
Parte da seção Programação Orientada a Objetos do Journey de C# da Coddy — lição 41 de 70.
A herança cria um acoplamento forte entre as classes. Quando um Car herda de Vehicle, qualquer alteração em Vehicle pode quebrar o Car. A composição oferece uma alternativa mais flexível: em vez de herdar o comportamento, uma classe contém objetos que fornecem esse comportamento.
Considere um personagem de jogo que pode se mover e atacar. Com herança, você pode criar uma hierarquia complexa. Com composição, você simplesmente dá ao personagem os componentes de que ele precisa:
public class MovementBehavior
{
public void Move() => Console.WriteLine("Moving...");
}
public class AttackBehavior
{
public void Attack() => Console.WriteLine("Attacking!");
}
public class Character
{
private MovementBehavior movement = new MovementBehavior();
private AttackBehavior attack = new AttackBehavior();
public void Move() => movement.Move();
public void Attack() => attack.Attack();
}A principal diferença é o relacionamento. A herança representa "é-um" (um Dog é um Animal), enquanto a composição representa "tem-um" (um Character tem um comportamento de movimento). A composição permite trocar comportamentos em tempo de execução, combinar capacidades livremente e evitar o problema da classe base frágil.
Use herança quando houver uma relação hierárquica genuína. Escolha composição quando precisar de flexibilidade, quando comportamentos puderem ser compartilhados entre classes não relacionadas ou quando desejar alterar o comportamento dinamicamente.
Desafio
FácilVamos construir um sistema de notificação que demonstra o poder da composição sobre a herança. Em vez de criar uma hierarquia complexa de classes de notificação, você comporá um NotificationSender fornecendo a ele componentes de comportamento intercambiáveis.
Você organizará seu código em três arquivos:
Behaviors.cs: Crie duas classes de comportamento no namespaceNotificationsque lidam com diferentes aspectos do envio de notificações:FormattingBehavior- possui um métodoFormat(string message)que retorna a mensagem envolvida com[ALERT]no início e[END]no finalDeliveryBehavior- possui um métodoDeliver(string channel, string message)que retorna uma string no formatoSending via {channel}: {message}
NotificationSender.cs: Crie uma classeNotificationSenderno namespaceNotificationsque utiliza composição para combinar comportamentos. Seu remetente deve:- Conter instâncias de ambos
FormattingBehavioreDeliveryBehaviorcomo campos privados - Ter uma propriedade
Channel(string) que armazena o nome do canal de entrega - Ter um construtor que aceita o nome do canal
- Ter um método
Send(string message)que primeiro formata a mensagem usando o comportamento de formatação, depois a entrega usando o comportamento de entrega e retorna o resultado final
- Conter instâncias de ambos
Program.cs: No seu arquivo principal, crie umNotificationSendercom um canal vindo da entrada, e então envie uma mensagem vinda da entrada. Imprima o resultado para mostrar como os comportamentos compostos trabalham juntos.
Você receberá duas entradas:
- O nome do canal de entrega (ex:
Email,SMS) - A mensagem a ser enviada
Imprima o resultado da chamada de Send() no seu remetente de notificação.
Por exemplo, se as entradas forem Email e Server is down, a saída deve ser:
Sending via Email: [ALERT] Server is down [END]Observe como o NotificationSender não herda de nada - ele simplesmente "tem" comportamentos de formatação e entrega. Essa abordagem de composição significa que você poderia facilmente trocar qualquer um dos comportamentos sem alterar a classe do remetente, ou reutilizar esses comportamentos em classes completamente diferentes!
Folha de consulta
A composição é uma alternativa à herança que cria código mais flexível ao fazer com que uma classe contenha objetos que fornecem comportamento, em vez de herdar o comportamento de uma classe pai.
Herança vs Composição:
- A herança representa relacionamentos do tipo "é um" (um Dog é um Animal)
- A composição representa relacionamentos do tipo "tem um" (um Character tem um comportamento de movimento)
Benefícios da Composição:
- Trocar comportamentos em tempo de execução
- Combinar capacidades livremente
- Evitar acoplamento rígido e problemas de classe base frágil
- Compartilhar comportamentos entre classes não relacionadas
Exemplo com componentes de comportamento:
public class MovementBehavior
{
public void Move() => Console.WriteLine("Moving...");
}
public class AttackBehavior
{
public void Attack() => Console.WriteLine("Attacking!");
}
public class Character
{
private MovementBehavior movement = new MovementBehavior();
private AttackBehavior attack = new AttackBehavior();
public void Move() => movement.Move();
public void Attack() => attack.Attack();
}Quando usar cada abordagem:
- Use herança para relacionamentos hierárquicos genuínos
- Escolha composição para flexibilidade, comportamentos compartilhados ou mudanças dinâmicas de comportamento
Experimente você mesmo
using System;
using Notifications;
class Program
{
public static void Main(string[] args)
{
// Ler a entrada
string channel = Console.ReadLine();
string message = Console.ReadLine();
// TODO: Criar um NotificationSender com o canal
// TODO: Enviar a mensagem e 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 Programação Orientada a Objetos
1Fundamentos de POO
Arquivos ExternosNamespaces e DiretivasIntrodução a Classes e ObjetosA Palavra-chave 'this'Métodos e ParâmetrosCampos vs PropriedadesConstrutoresInicializadores de ObjetosRecapitulação - Calculadora Simples4Herança
Sintaxe Básica de Herança (:)A Palavra-chave 'base'Palavras-chave Virtual e OverrideClasses SeladasA Classe Base 'object'Recapitulação - Hierarquia de Funcionários7Recursos Avançados
Sobrecarga de OperadoresIndexadores (this[])Sobrescrita de ToString()Métodos de ExtensãoRecapitulação - Lista Personalizada10Padrões de Projeto Parte 1
Introdução aos Padrões de ProjetoSingleton Thread-SafePadrão FactoryPadrão Observer (Eventos)Padrão Strategy2Propriedades e Membros Estáticos
Propriedades AutoimplementadasPropriedades de Apenas Leitura/EscritaCampos e Métodos EstáticosClasses EstáticasMembros com Corpo de Expressão5Polimorfismo e Interfaces
Polimorfismo: Compilação vs ExecuçãoInterface vs Classe AbstrataMúltiplas InterfacesInterfaces ExplícitasUpcasting e DowncastingRecapitulação - Calculadora de Formas8Conceitos Avançados de POO
Composição sobre HerançaGenerics (Classes e Métodos)Delegates e EventosAtributos e ReflectionIDisposable e a Instrução usingFundamentos de Injeção de Dependência11Padrões de Projeto Parte 2
Padrão CommandPadrão AdapterPadrão DecoratorPadrão Template MethodPadrão StatePadrão Composite3Arquitetura de Classes
Dados de Instância vs EstáticosPalavras-chave 'readonly' e 'const'Campos de SuporteRecapitulação - Gerenciador de Conta Bancária6Encapsulamento
Modificadores de AcessoPropriedades para EncapsulamentoImplementação de Ocultação de DadosPadrões de ImutabilidadeRecapitulação - Registros de Estudantes9Argumentos Variáveis
A Palavra-chave 'params'Parâmetros OpcionaisArgumentos NomeadosSobrecarga de Métodos