Swift Solutions - это серия статей, посвященных шаблонам проектирования в Swift. В каждой главе мы обсуждаем, что это за шаблон, когда он применяется и как его реализовать.
Составной шаблон обеспечивает согласованное поведение древовидных структур данных. Хотя у него есть нишевое приложение, оно очень полезно, когда применимо. В этом выпуске Swift Solutions вы узнаете, что такое шаблон, как его реализовать, и определите, когда его следует применять.
Определение
Составной паттерн включает древовидную иерархию коллекций и отдельных объектов. Это позволяет клиентам одинаково относиться к каждому иерархическому элементу.
Шаблон предполагает использование древовидной иерархии. Каждый элемент в дереве является либо отдельным объектом, либо коллекционным объектом. Объекты коллекции содержат массив элементов, который может представлять собой сочетание отдельных объектов и объектов коллекции.
Каждый объект в дереве имеет один и тот же интерфейс, поэтому клиенты могут рассматривать их как один и тот же тип.
Иллюстрация
Составной паттерн включает древовидную иерархию коллекций и отдельных объектов.
На приведенной выше иллюстрации у нас есть каталог папок и mp3. Папка просто содержит массив файлов. Коллекция каждой папки может содержать как mp3, так и дополнительные папки. Эта иллюстрация прекрасно соответствует каждой части нашего определения:
- Иерархия - ›Справочник
- Объект коллекции - ›Папка
- Индивидуальный объект - ›mp3 файл
Это позволяет клиентам одинаково относиться к каждому иерархическому элементу.
Все файлы в иерархии имеют общий интерфейс, независимо от того, папка это или mp3. Например, и mp3, и папки - это файлы, которые можно переименовывать, перемещать и дублировать. Поэтому разумно ожидать соответствия протоколу, который требует реализации файлового поведения.
Важным выводом является то, что клиенты могут одинаково обращаться с группами и отдельными частями. Нет необходимости постоянно проверять типы каждого файла перед его использованием. Это прямо результат того, что элементы являются производными от одного и того же протокола или базового класса.
UML
Компонент: абстракции, обеспечивающие общий интерфейс для коллекций и отдельных объектов. Все элементы в дереве должны быть производными от протокола компонента или базового класса.
Примитив: ранее назывался «индивидуальный объект». Примитивы - это просто компоненты в дереве, не содержащие дочерних компонентов.
Составной: ранее назывался «объект коллекции». Композиты - это объекты, которые содержат массив компонентов. Хотя составные и примитивные объекты имеют один и тот же интерфейс, композиты содержат дополнительные методы для управления своими дочерними объектами.
Просто предупреждаем: есть несколько способов обращения к нашим конкретным компонентам:
Лично я предпочитаю термины «составной» и «примитивный». Разобравшись с этим, давайте перейдем к коду!
Реализация
В нашем примере мы будем распределять премиальные выплаты между отделами компании, которые в конечном итоге перейдут к сотрудникам. В этом сценарии отделы и сотрудники функционируют как наши составные части и примитивы соответственно.
Составная часть
protocol Payee {
var name: String { get }
var bonusAmount: Double { get }
func receive(bonus: Double)
}
Сначала мы создаем Payee
протокол, которому будут соответствовать наши составные и примитивные объекты. Это будет средство, с помощью которого мы будем относиться к ним единообразно.
Работник
class Employee: Payee {
// 1
private var _name: String
private var _bonusAmount: Double = 0
var name: String { return _name }
var bonusAmount: Double { return _bonusAmount }
init(name: String) {
self._name = name
}
// 2
func receive(bonus: Double) {
_bonusAmount += bonus
}
}
Давайте подробно рассмотрим наш код:
- Мы создаем сотрудника, соответствующего
Payee
, реализуя свойстваname
иbonusAmount
. receive(bonus:)
настроен как единственный способ манипулировать суммой бонусов каждого сотрудника.
отделение
Теперь давайте реализуем Department
:
class Department: Payee {
private var _name: String
// 1
private var _bonusAmount: Double {
get {
var bonus = 0.0
for subunit in subunits {
bonus += subunit.bonusAmount
}
return bonus
}
set {
let splitCount = Double(subunits.count)
let splitBonus = newValue / splitCount
for subunit in subunits {
subunit.receive(bonus: splitBonus)
}
}
}
// 2
private let subunits: [Payee]
var name: String { return _name }
var bonusAmount: Double { return _bonusAmount }
init(name: String, subunits: [Payee] = []) {
self._name = name
self.subunits = subunits
}
func receive(bonus: Double) {
_bonusAmount += bonus
}
}
В деталях, вот что мы достигли при создании класса отдела:
- Создать частную собственность
_bonusAmount
. Его получатель вычисляет бонус, обращаясь кsubunits
- набору подразделений и сотрудников - и возвращая сумму их бонусных сумм. Кроме того, мы включаем сеттер, который делит любой назначенный бонус и распределяет его по каждому дочернему компоненту. Department
действует как наша совокупность, имея набор получателей.
Обратите внимание, что в отделе (и его подразделениях) нет никаких бонусов; они всегда распределяют бонус до тех пор, пока он не достигнет сотрудника.
Также стоит повторить, что композиты выходят за рамки согласования с общим протоколом. Они также реализуют методы для управления дочерними компонентами. Ожидается наличие таких методов, как add(Payee:)
и remove(Payee:)
, но это выходит за рамки этого примера. Мы просто устанавливаем дочерние компоненты во время инициализации и предотвращаем дальнейшее изменение, делая subunits
частной константой.
Использование составного паттерна
// 1
let joan = Employee(name: "Joan")
let tom = Employee(name: "Tom")
let cleo = Employee(name: "Cleo")
let alex = Employee(name: "Alex")
// 2
let graphicDesignDepartment = Department(name: "Graphic Design", subunits: [cleo, alex])
let marketingDepartment = Department(name: "Marketing", subunits: [joan, tom, graphicDesignDepartment])
// 3
marketingDepartment.receive(bonus: 1000)
Вот что мы сделали шаг за шагом:
- Создайте несколько сотрудников, часть из которых будет использоваться при создании отделов.
- Создайте простую древовидную иерархию: создайте маркетинговый отдел, содержащий отдел графического дизайна, с разбросанными по нему сотрудниками.
- Дайте отделу маркетинга бонус в размере 1000 долларов. Он автоматически распределяется между его подразделениями и сотрудниками. Подотделы далее делят премию, пока она не достигнет сотрудника.
Сценарий использования
Как обычно, важно понимать, когда применяется шаблон. Обратите внимание на следующие индикаторы:
- Существует древовидная иерархия коллекций и отдельных объектов.
- Когда желательно обрабатывать объекты в дереве единообразно, а не запрашивать тип каждого компонента перед выполнением действия.
- Когда составной элемент и примитив имеют схожую функциональность.
- Каждый раз, когда ресурс необходимо распределить по древовидной иерархии.
Заключение
Ты сделал это! Теперь вы можете эффективно работать с древовидными структурами данных.
Каждый шаблон проектирования - это мощный инструмент в вашем наборе инструментов для программирования. Всегда расширять доступные вам инструменты - большое достижение. Поздравляю, продолжайте учиться до следующего раза!
Первоначально опубликовано на emanharout.com.