Menu
Coddy logo textTech

Паттерн Компоновщик

Часть раздела Объектно-ориентированное программирование путешествия по GO на Coddy. Урок 96 из 107.

Паттерн Composite позволяет единообразно работать с отдельными объектами и группами объектов. В то время как State изменяет поведение в зависимости от внутреннего состояния, Composite создаёт древовидные структуры, в которых и конечные элементы, и контейнеры используют один и тот же интерфейс.

Этот шаблон идеально подходит для иерархических структур, таких как файловые системы, организационные диаграммы или компоненты пользовательского интерфейса. В Go мы определяем общий интерфейс, который реализуют как отдельные элементы, так и контейнеры:

type Component interface {
    GetSize() int
}

type File struct {
    Name string
    Size int
}

func (f File) GetSize() int {
    return f.Size
}

Составной объект (контейнер) содержит Children и реализует тот же interface, объединяя их результаты:

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
}

Теперь вы можете вкладывать папки в папки, и вызов GetSize() работает одинаково независимо от того, является ли это отдельным файлом или целым деревом каталогов:

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())  // 175

Composite идеально подходит, когда нужно представить иерархии «часть — целое» и вы хотите, чтобы клиенты одинаково обрабатывали individual объекты и композиции.

challenge icon

Задание

Легко

Давайте построим систему организационной структуры с использованием паттерна Composite! Вы создадите структуру, в которой и отдельные employee, и department (содержащие employees или другие department) могут обрабатываться единообразно — это идеально подходит для подсчёта общей зарплаты в любой части организации.

Вы организуете код в трёх файлах:

  • component.go: Define общий interface, который будут implement и отдельные элементы, и группы.

    Создайте interface OrgComponent с двумя методами:

    • GetSalary() int: возвращает общую зарплату этого component
    • GetName() string: возвращает имя employee или department
  • organization.go: Implement типы leaf (отдельный employee) и composite (department).

    Создайте struct Employee с полями Name (string) и Salary (int). Его GetSalary() возвращает его собственную зарплату, а GetName() — его имя.

    Создайте struct Department с полем Name (string) и срезом Members, содержащим элементы OrgComponent. Добавьте метод Add(c OrgComponent) для добавления участников. Его GetSalary() должен возвращать сумму зарплат всех участников, а GetName() — имя department.

  • main.go: Создайте структуру организации и вычислите зарплаты.

    Прочитайте количество employees. Для каждого employee прочитайте его имя и зарплату, создавая экземпляры Employee. Затем прочитайте количество departments. Для каждого department прочитайте его имя и количество индексов участников, после чего — эти индексы (начиная с 0, они ссылаются на ранее созданные employees или departments в порядке creation).

    После построения структуры прочитайте final index и выведите имя этого component и общую зарплату в формате: [name]: [salary]

Будут предоставлены следующие входные данные:

  • Количество employees, затем имя и зарплата каждого employee в отдельных строках
  • Количество departments, затем имя каждого department, количество участников и индексы участников
  • Final index для запроса

Например, для:

3
Alice
50000
Bob
60000
Carol
55000
1
Engineering
3
0
1
2
3

Ваш вывод должен быть таким:

Engineering: 165000

А для:

4
Alice
50000
Bob
60000
Carol
55000
Dave
70000
2
Dev
2
0
1
QA
2
2
3
4

Ваш вывод должен быть таким:

QA: 125000

А для:

2
Alice
50000
Bob
60000
0
1

Ваш вывод должен быть таким:

Bob: 60000

Обратите внимание, что GetSalary() работает одинаково, вызываете ли вы его для отдельного employee или целого department: паттерн Composite позволяет единообразно обрабатывать отдельные элементы и группы через один и тот же interface!

Попробуйте сами

package main

import "fmt"

func main() {
	// Прочитать количество сотрудников
	var numEmployees int
	fmt.Scanln(&numEmployees)

	// Сохранить все компоненты (сотрудники и отделы) в порядке создания
	var components []OrgComponent

	// TODO: Прочитать имя и зарплату каждого сотрудника
	// Создать экземпляры Employee и добавить их в слайс components
	for i := 0; i < numEmployees; i++ {
		var name string
		var salary int
		fmt.Scanln(&name)
		fmt.Scanln(&salary)
		// TODO: Создать сотрудника и добавить в components
	}

	// Прочитать количество отделов
	var numDepartments int
	fmt.Scanln(&numDepartments)

	// TODO: Прочитать название каждого отдела, количество участников и индексы участников
	// Создать экземпляры Department, добавить участников по индексу и добавить в components
	for i := 0; i < numDepartments; i++ {
		var deptName string
		var memberCount int
		fmt.Scanln(&deptName)
		fmt.Scanln(&memberCount)
		// TODO: Создать отдел, прочитать индексы участников, добавить участников, добавить в components
	}

	// Прочитать конечный индекс для запроса
	var queryIndex int
	fmt.Scanln(&queryIndex)

	// TODO: Вывести имя компонента и общую зарплату в формате: [name]: [salary]
	// Пример: fmt.Printf("%s: %d\n", name, salary)
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование

Потренируйтесь самостоятельно: Онлайн-компилятор Go