Паттерн Компоновщик
Часть раздела Объектно-ориентированное программирование путешествия по 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()) // 175Composite идеально подходит, когда нужно представить иерархии «часть — целое» и вы хотите, чтобы клиенты одинаково обрабатывали individual объекты и композиции.
Задание
ЛегкоДавайте построим систему организационной структуры с использованием паттерна Composite! Вы создадите структуру, в которой и отдельные employee, и department (содержащие employees или другие department) могут обрабатываться единообразно — это идеально подходит для подсчёта общей зарплаты в любой части организации.
Вы организуете код в трёх файлах:
component.go: Define общий interface, который будут implement и отдельные элементы, и группы.Создайте interface
OrgComponentс двумя методами:GetSalary() int: возвращает общую зарплату этого componentGetName() 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)
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП в Go
Внешние файлыРабочее пространство и модули GoПакеты и импортыЭкспортируемые и неэкспортируемые именаВведение в ООП в GoСтруктуры как классыОпределение методов структурПолучатели-указатели и получатели-значенияИнициализация структурФункции-конструкторыИтоги — Простой калькулятор4Интерфейсы
Введение в интерфейсыНеявная реализацияИнтерфейс как контрактПустой интерфейс (any)Утверждение типаПереключатель типовКомпозиция интерфейсовИнтерфейсы Stringer и ErrorПовторение: Калькулятор фигур7Инкапсуляция
Экспортируемые и неэкспортируемые поляИнкапсуляция на уровне пакетовГеттеры и сеттерыСокрытие информации в GoИтоги — Записи о студентах10Обобщения (Generics) (Go 1.18+)
Введение в GenericsПараметры типовОграничения типовОбобщенные структурыОбходной путь для обобщенных методовИтоги — Обобщенная коллекция13Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonПаттерн FactoryПаттерн Abstract FactoryПаттерн ObserverПаттерн Strategy2Глубокое погружение в типы и структуры
Базовые и составные типыОпределение пользовательских типовТеги структурАнонимные структурыВложенные структурыНулевые значения и значения по умолчаниюПовторение — Контактная книга5Композиция вместо наследования
Почему в Go нет наследованияОсновы встраивания структурПродвижение методовВстраивание нескольких структурВстраивание против агрегацииЗатенение встроенных методовИтоги — Иерархия сотрудников8Обработка ошибок и ООП
Интерфейс errorПользовательские типы ошибокОбертывание ошибок (fmt.Errorf)Sentinel-ошибкиerrors.Is() и errors.As()Panic, Defer и RecoverИтоги — Парсер файлов11Стандартная библиотека и ООП
io.Reader и io.Writersort.InterfaceИнтерфейс fmt.Stringerencoding/json со структурамиИнтерфейс http.HandlerПовторение: модели REST API14Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикMiddleware как Декоратор3Указатели и память
Основы указателей в GoУказатели на структурыПередача по значению и по ссылкеФункция new()Сборка мусора в GoПовторение: Конструктор связного списка6Полиморфизм в Go
Полиморфизм через интерфейсыУтиная типизация в GoПравила реализации интерфейсовПолиморфные коллекцииВнедрение зависимостейИтоги — Обработчик платежей9Конкурентность и ООП
Основы горутинКаналы и взаимодействиеБуферизованные и небуферизованные каналыОператор selectsync.Mutex и sync.RWMutexsync.WaitGroupПроектирование потокобезопасных структурПовторение — Worker PoolПотренируйтесь самостоятельно: Онлайн-компилятор Go