Padrão Composite
Parte da seção Programação Orientada a Objetos do Journey de GO da Coddy. Lição 96 de 107.
O padrão Composite permite tratar objetos individuais e grupos de objetos de maneira uniforme. Enquanto o State altera o comportamento com base no estado interno, o Composite constrói estruturas em árvore nas quais tanto as folhas quanto os contêineres compartilham a mesma interface.
Este padrão é perfeito para estruturas hierárquicas, como sistemas de arquivos, organogramas ou componentes de UI. Em Go, definimos uma interface comum que tanto os itens individuais quanto os contêineres implementam:
type Component interface {
GetSize() int
}
type File struct {
Name string
Size int
}
func (f File) GetSize() int {
return f.Size
}O composto (contêiner) contém elementos filhos e implementa a mesma interface agregando seus resultados:
type Folder struct {
Name string
Children []Component
}
func (f *Folder) Add(c Component) {
f.Children = append(f.Children, c)
}
func (f Folder) GetSize() int {
total := 0
for _, child := range f.Children {
total += child.GetSize()
}
return total
}Agora você pode aninhar pastas dentro de pastas, e chamar GetSize() funciona de forma idêntica, seja para um único arquivo ou para uma árvore de diretórios inteira:
docs := &Folder{Name: "docs"}
docs.Add(File{Name: "readme.txt", Size: 100})
docs.Add(File{Name: "notes.txt", Size: 50})
root := &Folder{Name: "root"}
root.Add(docs)
root.Add(File{Name: "config.json", Size: 25})
fmt.Println(root.GetSize()) // 175Composite é ideal quando você precisa representar hierarquias parte-todo e quer que os clientes tratem objetos individuais e composições de forma idêntica.
Desafio
FácilVamos criar um sistema de organograma usando o padrão Composite! Você criará uma estrutura na qual tanto funcionários individuais quanto departamentos (que contêm funcionários ou outros departamentos) podem ser tratados de maneira uniforme, perfeita para calcular os salários totais de qualquer parte da organização.
Você organizará seu código em três arquivos:
component.go: Defina a interface comum que tanto os indivíduos quanto os grupos implementarão.Crie uma interface
OrgComponentcom dois métodos:GetSalary() int: retorna o salário total deste componenteGetName() string: retorna o nome do funcionário ou departamento
organization.go: Implemente os tipos folha (funcionário individual) e composto (departamento).Crie uma struct
Employeecom os camposName(string) eSalary(int). SeuGetSalary()retorna seu próprio salário, eGetName()retorna seu nome.Crie uma struct
Departmentcom um campoName(string) e uma sliceMembersque armazena itensOrgComponent. Adicione um métodoAdd(c OrgComponent)para adicionar membros. SeuGetSalary()deve retornar a soma dos salários de todos os membros, eGetName()retorna o nome do departamento.main.go: Construa uma estrutura organizacional e calcule os salários.Leia o número de funcionários. Para cada funcionário, leia seu nome e salário, criando instâncias de
Employee. Em seguida, leia o número de departamentos. Para cada departamento, leia seu nome e a quantidade de índices de membros, seguida por esses índices (baseados em 0, referindo-se aos funcionários ou departamentos criados anteriormente, na ordem de criação).Depois de construir a estrutura, leia um índice final e imprima o nome e o salário total desse componente no formato:
[name]: [salary]
As seguintes entradas serão fornecidas:
- Número de funcionários, seguido pelo nome e salário de cada funcionário em linhas separadas
- Número de departamentos, seguido pelo nome de cada departamento, pela quantidade de membros e pelos índices dos membros
- Índice final a ser consultado
Por exemplo, dado:
3
Alice
50000
Bob
60000
Carol
55000
1
Engineering
3
0
1
2
3Sua saída deve ser:
Engineering: 165000E, dado:
4
Alice
50000
Bob
60000
Carol
55000
Dave
70000
2
Dev
2
0
1
QA
2
2
3
4Sua saída deve ser:
QA: 125000E, dado:
2
Alice
50000
Bob
60000
0
1Sua saída deve ser:
Bob: 60000Observe como GetSalary() funciona de forma idêntica, independentemente de ser chamado em um único funcionário ou em um departamento inteiro: o padrão Composite permite tratar indivíduos e grupos de maneira uniforme por meio da mesma interface!
Experimente você mesmo
package main
import "fmt"
func main() {
// Ler o número de funcionários
var numEmployees int
fmt.Scanln(&numEmployees)
// Armazenar todos os componentes (funcionários e departamentos) na ordem de criação
var components []OrgComponent
// TODO: Ler o nome e o salário de cada funcionário
// Criar instâncias de Employee e adicioná-las ao slice components
for i := 0; i < numEmployees; i++ {
var name string
var salary int
fmt.Scanln(&name)
fmt.Scanln(&salary)
// TODO: Criar employee e adicionar a components
}
// Ler o número de departamentos
var numDepartments int
fmt.Scanln(&numDepartments)
// TODO: Ler o nome de cada departamento, a quantidade de membros e os índices dos membros
// Criar instâncias de Department, adicionar membros por índice e adicionar a components
for i := 0; i < numDepartments; i++ {
var deptName string
var memberCount int
fmt.Scanln(&deptName)
fmt.Scanln(&memberCount)
// TODO: Criar department, ler índices dos membros, adicionar membros, adicionar a components
}
// Ler o índice final a consultar
var queryIndex int
fmt.Scanln(&queryIndex)
// TODO: Imprimir o nome do componente e o salário total no formato: [name]: [salary]
// Exemplo: fmt.Printf("%s: %d\n", name, salary)
}
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 OOP em Go
Arquivos ExternosWorkspace e Módulos do GoPacotes e ImportsNomes Exportados vs. Não ExportadosIntrodução à OOP em GoStructs como ClassesDefinindo Métodos em StructsReceptores de Ponteiro vs. de ValorInicialização de StructsFunções ConstrutorasRecapitulação — Calculadora Simples4Interfaces
Introdução às InterfacesImplementação ImplícitaInterface como ContratoInterface Vazia (any)Asserção de TipoSwitch de TipoComposição de InterfacesInterfaces Stringer e ErrorRecapitulação - Calculadora de Formas7Encapsulamento
Campos Exportados vs. Não ExportadosEncapsulamento no Nível de PacoteMétodos Getters e SettersOcultação de Informações em GoRecapitulação - Registros de Alunos10Genéricos (Go 1.18+)
Introdução aos genéricosParâmetros de tipoRestrições de tipoStructs genéricasAlternativa para métodos genéricosRecapitulação - Coleção genérica13Padrões de Projeto — Parte 1
Introdução aos Padrões de ProjetoPadrão SingletonPadrão FactoryPadrão Abstract FactoryPadrão ObserverPadrão Strategy2Tipos e Structs em Profundidade
Tipos Básicos e CompostosDefinições de Tipos PersonalizadosTags de StructStructs AnônimasStructs AninhadasValores Zero e PadrõesRecapitulação - Livro de Contatos5Composição em vez de herança
Por que Go não tem herançaNoções básicas de incorporação de structsPromoção de métodosIncorporação de várias structsIncorporação vs agregaçãoOcultação de métodos incorporadosRecapitulação - Hierarquia de funcionários8Tratamento de Erros e OOP
A interface errorTipos de Erro PersonalizadosEncapsulamento de Erros (fmt.Errorf)Erros Sentinelaerrors.Is() e errors.As()Panic, Defer e RecoverRecapitulação - Analisador de Arquivos11Biblioteca Padrão e POO
io.Reader e io.Writersort.InterfaceInterface fmt.Stringerencoding/json com StructsInterface http.HandlerRevisão - Modelos de API REST14Padrões de Projeto — Parte 2
Padrão de ComandoPadrão AdapterPadrão DecoratorPadrão de Método TemplatePadrão de EstadoPadrão CompositeMiddleware como Decorator3Ponteiros e Memória
Fundamentos de Ponteiros em GoPonteiros para StructsPassagem por Valor vs. ReferênciaA Função new()Coleta de Lixo em GoRecapitulação - Construtor de Lista Ligada6Polimorfismo em Go
Polimorfismo via InterfacesDuck Typing em GoRegras de Satisfação de InterfacesColeções PolimórficasInjeção de DependênciasRecapitulação - Processador de Pagamentos9Concorrência e OOP
Fundamentos de GoroutinesCanais e comunicaçãoCanais com e sem bufferInstrução selectsync.Mutex e sync.RWMutexsync.WaitGroupDesign de structs thread-safeRevisão - Worker PoolPratique por conta própria: Compilador de Go online