Menu
Coddy logo textTech

Padrão Composite

Parte da seção Programação Orientada a Objetos do Journey de Dart da Coddy — lição 99 de 110.

O padrão Composite permite que você trate objetos individuais e grupos de objetos de forma uniforme. Ele compõe objetos em estruturas de árvore onde tanto elementos únicos quanto coleções de elementos compartilham a mesma interface. Isso é perfeito para representar hierarquias como sistemas de arquivos, organogramas ou componentes de UI.

O padrão possui dois tipos de participantes: nós Leaf (objetos individuais sem filhos) e nós Composite (contêineres que contêm outros componentes). Ambos implementam uma interface Component comum:

abstract class FileSystemItem {
  String get name;
  int getSize();
  void display([String indent = '']);
}

class File implements FileSystemItem {
  @override
  final String name;
  final int size;

  File(this.name, this.size);

  @override
  int getSize() => size;

  @override
  void display([String indent = '']) {
    print('$indent- $name ($size KB)');
  }
}

class Folder implements FileSystemItem {
  @override
  final String name;
  final List<FileSystemItem> _children = [];

  Folder(this.name);

  void add(FileSystemItem item) => _children.add(item);

  @override
  int getSize() => _children.fold(0, (sum, item) => sum + item.getSize());

  @override
  void display([String indent = '']) {
    print('$indent+ $name/');
    for (var child in _children) {
      child.display('$indent  ');
    }
  }
}

void main() {
  var docs = Folder('Documents');
  docs.add(File('resume.pdf', 150));
  docs.add(File('photo.jpg', 2400));

  var projects = Folder('Projects');
  projects.add(File('main.dart', 25));
  docs.add(projects);

  docs.display();
  print('Total: ${docs.getSize()} KB');
}

A classe Folder pode conter tanto objetos File quanto outros objetos Folder. Quando você chama getSize() em uma pasta, ela calcula recursivamente o tamanho total de todo o seu conteúdo. O código cliente não precisa saber se está trabalhando com um único arquivo ou com uma hierarquia inteira de pastas — ambos respondem aos mesmos métodos.

challenge icon

Desafio

Fácil

Vamos 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 e outros departamentos) podem ser tratados de forma uniforme - perfeito para representar hierarquias de empresas.

Você organizará seu código em dois arquivos:

  • organization.dart: Este arquivo contém sua interface de componente e as classes leaf (folha) e composite (composta). Crie uma classe abstrata OrganizationUnit com um getter name, um método getSalaryBudget() que retorna um int, e um método display([String indent = '']) para mostrar a hierarquia. Construa uma classe Employee (a folha) que implementa OrganizationUnit. Ela deve ter um nome e salário, com getSalaryBudget() retornando o salário do funcionário. Seu display() deve imprimir [indent]- [name] ($[salary]). Em seguida, crie uma classe Department (a composta) que também implementa OrganizationUnit. Ela deve ter um nome e manter uma lista de filhos OrganizationUnit. Adicione um método add() para incluir funcionários ou subdepartamentos. Seu getSalaryBudget() deve somar recursivamente os orçamentos de todos os filhos. Seu display() deve imprimir [indent]+ [name]/ seguido pela exibição de cada filho com recuo aumentado (adicione dois espaços).
  • main.dart: Importe seu arquivo de organização e construa uma estrutura de empresa. Crie um departamento Engineering contendo dois funcionários: Alice com salário 80000 e Bob com salário 75000. Crie um departamento QA com uma funcionária: Carol com salário 65000. Agora crie um departamento Technology e adicione tanto Engineering quanto QA como subdepartamentos. Chame display() no departamento Technology e, em seguida, imprima Total Budget: $ seguido pelo orçamento salarial total.

A beleza do padrão Composite é que Technology.getSalaryBudget() calcula automaticamente o total em todos os departamentos e funcionários aninhados - o código do cliente não precisa conhecer a estrutura interna!

Saída esperada:

+ Technology/
  + Engineering/
    - Alice ($80000)
    - Bob ($75000)
  + QA/
    - Carol ($65000)
Total Budget: $220000

Folha de consulta

O padrão Composite permite que você trate objetos individuais e grupos de objetos de forma uniforme, compondo-os em estruturas de árvore onde tanto elementos únicos quanto coleções compartilham a mesma interface.

O padrão consiste em:

  • Component: Uma interface abstrata que define operações comuns
  • Leaf: Objetos individuais sem filhos
  • Composite: Contêineres que contêm outros componentes (folhas ou composites)

Exemplo: Sistema de Arquivos

abstract class FileSystemItem {
  String get name;
  int getSize();
  void display([String indent = '']);
}

class File implements FileSystemItem {
  @override
  final String name;
  final int size;

  File(this.name, this.size);

  @override
  int getSize() => size;

  @override
  void display([String indent = '']) {
    print('$indent- $name ($size KB)');
  }
}

class Folder implements FileSystemItem {
  @override
  final String name;
  final List<FileSystemItem> _children = [];

  Folder(this.name);

  void add(FileSystemItem item) => _children.add(item);

  @override
  int getSize() => _children.fold(0, (sum, item) => sum + item.getSize());

  @override
  void display([String indent = '']) {
    print('$indent+ $name/');
    for (var child in _children) {
      child.display('$indent  ');
    }
  }
}

void main() {
  var docs = Folder('Documents');
  docs.add(File('resume.pdf', 150));
  docs.add(File('photo.jpg', 2400));

  var projects = Folder('Projects');
  projects.add(File('main.dart', 25));
  docs.add(projects);

  docs.display();
  print('Total: ${docs.getSize()} KB');
}

A classe Folder pode conter tanto objetos File quanto outros objetos Folder. Quando você chama getSize() em uma pasta, ela calcula recursivamente o tamanho total de todo o seu conteúdo. O código do cliente trata arquivos individuais e hierarquias inteiras de pastas de forma uniforme.

Experimente você mesmo

import 'organization.dart';

void main() {
  // TODO: Criar um departamento de Engineering
  // TODO: Adicionar Alice com salário 80000
  // TODO: Adicionar Bob com salário 75000
  
  // TODO: Criar um departamento de QA
  // TODO: Adicionar Carol com salário 65000
  
  // TODO: Criar um departamento de Technology
  // TODO: Adicionar Engineering e QA como subdepartamentos
  
  // TODO: Chamar display() no departamento de Technology
  
  // TODO: Imprimir "Total Budget: $" seguido pelo orçamento total de salários
}
quiz iconTeste seus conhecimentos

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