Padrão Composite
Parte da seção Programação Orientada a Objetos do Journey de C# da Coddy — lição 61 de 70.
O padrão Composite é um padrão estrutural que permite compor objetos em estruturas de árvore e tratar objetos individuais e grupos de objetos de maneira uniforme. É perfeito para representar hierarquias parte-todo onde você deseja trabalhar tanto com itens individuais quanto com coleções através da mesma interface.
O padrão possui três componentes principais: uma interface de componente que define operações comuns, classes folha que representam objetos individuais e classes compostas que contêm filhos e implementam a mesma interface:
public interface IFileSystemItem
{
string Name { get; }
int GetSize();
}
public class File : IFileSystemItem
{
public string Name { get; }
private int _size;
public File(string name, int size)
{
Name = name;
_size = size;
}
public int GetSize() => _size;
}
public class Folder : IFileSystemItem
{
public string Name { get; }
private List<IFileSystemItem> _items = new List<IFileSystemItem>();
public Folder(string name) => Name = name;
public void Add(IFileSystemItem item) => _items.Add(item);
public int GetSize() => _items.Sum(item => item.GetSize());
}A beleza deste padrão é que os clientes não precisam saber se estão trabalhando com um único arquivo ou com uma hierarquia de pastas inteira:
var root = new Folder("root");
root.Add(new File("doc.txt", 100));
var subFolder = new Folder("images");
subFolder.Add(new File("photo.jpg", 500));
subFolder.Add(new File("icon.png", 50));
root.Add(subFolder);
Console.WriteLine(root.GetSize()); // 650O padrão Composite é ideal para sistemas de arquivos, árvores de componentes de UI, organogramas ou qualquer estrutura onde contêineres e conteúdos devam ser tratados da mesma forma. Ele simplifica o código do cliente ao eliminar a necessidade de distinguir entre elementos simples e complexos.
Desafio
FácilVamos construir um sistema de organograma usando o padrão Composite. Você criará uma estrutura onde tanto funcionários individuais quanto departamentos (que contêm funcionários ou outros departamentos) podem ser tratados de forma uniforme, permitindo calcular o custo salarial total em qualquer nível da hierarquia.
Você organizará seu código em três arquivos:
OrgComponent.cs: Defina uma interfaceIOrgComponentno namespaceOrganizationcom dois membros: uma propriedadeName(string) e um métodoGetSalary()que retorna um número inteiro. Esta interface representa o contrato comum que tanto os funcionários individuais quanto os departamentos seguirão.OrgClasses.cs: Crie duas classes no mesmo namespace:Employee- uma classe folha (leaf) que representa um trabalhador individual. Ela recebe um nome e um salário em seu construtor e implementa a interface retornando seu próprio salário.Department- uma classe composta (composite) que representa um grupo. Ela recebe um nome em seu construtor e mantém uma lista de itensIOrgComponent. Inclua um métodoAdd(IOrgComponent component)para adicionar membros. SeuGetSalary()deve retornar a soma dos salários de todos os seus membros.
Program.cs: Construa uma estrutura organizacional baseada na entrada e calcule o salário total. Você criará departamentos e funcionários, os aninhará adequadamente e demonstrará como o padrão composite permite tratar toda a hierarquia de forma uniforme.
Você receberá as seguintes entradas:
- O número de componentes a serem criados
- Para cada componente: o tipo (
employeeoudepartment), seguido pelo nome e, para funcionários, seu salário. Para departamentos, você também receberá o nome do departamento pai (ourootse for um departamento de nível superior)
Formato de entrada para cada componente:
employee
{name}
{salary}
{parent_department}
department
{name}
{parent_department}Após construir a estrutura, imprima o salário total do departamento raiz com o formato {root_name} Total Salary: {amount}.
Por exemplo, se as entradas forem:
5
department
Engineering
root
employee
Alice
5000
Engineering
employee
Bob
4500
Engineering
department
QA
Engineering
employee
Charlie
4000
QAA saída deve ser:
Engineering Total Salary: 13500O departamento Engineering contém Alice (5000), Bob (4500) e o departamento QA. QA contém Charlie (4000). Quando você chama GetSalary() em Engineering, ele calcula recursivamente o total de todos os componentes aninhados - demonstrando como o padrão Composite permite trabalhar com estruturas de árvore complexas através de uma interface simples e uniforme!
Folha de consulta
O padrão Composite é um padrão estrutural que compõe objetos em estruturas de árvore e trata objetos individuais e grupos uniformemente por meio da mesma interface. É ideal para hierarquias parte-todo.
O padrão possui três componentes principais:
- Interface Component: Define operações comuns
- Classes Leaf: Representam objetos individuais
- Classes Composite: Contêm filhos e implementam a mesma interface
Exemplo básico de implementação:
// Interface Component
public interface IFileSystemItem
{
string Name { get; }
int GetSize();
}
// Classe Leaf
public class File : IFileSystemItem
{
public string Name { get; }
private int _size;
public File(string name, int size)
{
Name = name;
_size = size;
}
public int GetSize() => _size;
}
// Classe Composite
public class Folder : IFileSystemItem
{
public string Name { get; }
private List<IFileSystemItem> _items = new List<IFileSystemItem>();
public Folder(string name) => Name = name;
public void Add(IFileSystemItem item) => _items.Add(item);
public int GetSize() => _items.Sum(item => item.GetSize());
}Uso - os clientes trabalham com itens individuais e coleções de forma uniforme:
var root = new Folder("root");
root.Add(new File("doc.txt", 100));
var subFolder = new Folder("images");
subFolder.Add(new File("photo.jpg", 500));
subFolder.Add(new File("icon.png", 50));
root.Add(subFolder);
Console.WriteLine(root.GetSize()); // 650O padrão Composite é útil para sistemas de arquivos, árvores de componentes de UI, organogramas ou qualquer estrutura onde contêineres e conteúdos devam ser tratados da mesma forma.
Experimente você mesmo
using System;
using System.Collections.Generic;
using Organization;
class Program
{
public static void Main(string[] args)
{
int n = Convert.ToInt32(Console.ReadLine());
// TODO: Criar um dicionário para armazenar departamentos por nome
// Isso ajudará você a encontrar departamentos pai ao adicionar componentes
Dictionary<string, Department> departments = new Dictionary<string, Department>();
// TODO: Manter o controle do departamento raiz (root)
Department root = null;
for (int i = 0; i < n; i++)
{
string type = Console.ReadLine();
if (type == "department")
{
string name = Console.ReadLine();
string parent = Console.ReadLine();
// TODO: Criar o departamento
// TODO: Adicioná-lo ao dicionário de departamentos
// TODO: Se parent for "root", este é o departamento raiz
// TODO: Caso contrário, encontre o departamento pai e adicione este departamento a ele
}
else if (type == "employee")
{
string name = Console.ReadLine();
int salary = Convert.ToInt32(Console.ReadLine());
string parent = Console.ReadLine();
// TODO: Criar o funcionário
// TODO: Encontrar o departamento pai e adicionar o funcionário a ele
}
}
// TODO: Imprimir o salário total do departamento raiz
// Formato: "{root_name} Total Salary: {amount}"
Console.WriteLine($"{root.Name} Total Salary: {root.GetSalary()}");
}
}
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