Swift Solutions - это серия статей, посвященных шаблонам проектирования. В каждом посте мы обсуждаем, что это за шаблон, когда он применяется и как реализовать его с помощью Swifty.

Отойдя от творческих паттернов, сегодня мы рассмотрим паттерн моста. Он определен «Бандой четырех» как шаблон, который «отделяет абстракцию от ее реализации, так что они могут различаться независимо друг от друга». Я не знаю, как вы, но это определение было мне не по зубам. в первый раз, когда я прочитал это, я решил разбирать определение по мере продвижения. К концу этой статьи у вас будет четкое представление о том, когда применяется этот шаблон и как его легко реализовать.

Сценарий использования

Шаблон «Мост» используется для решения проблемы, которую программисты называют «взрывающейся иерархией классов». Расширение иерархии классов происходит, когда количество классов резко увеличивается с появлением дополнительных функций.

Поскольку распознавание того, когда использовать шаблон, почти так же важно, как и его изучение, мы проиллюстрируем упрощенную версию проблемы, а затем рассмотрим код, который демонстрирует проблему. Наконец, мы расскажем, как реализовать решение.

Понимание расширяющейся иерархии классов

Я хочу, чтобы вы обратили внимание на абстрактные гамбургеры и их конкретное применение в качестве еды. Допустим, мы хотели добавить новый бургер, сколько дополнительных классов вам нужно создать?

Если вы ответите на три, вы будете правы:

А теперь представьте, если бы мы добавили в смесь кусочек салата. Для этого потребуются дополнительные три класса (по одному на каждый гамбургер, с которым он должен быть реализован).

Проблема с течением времени

Растущая иерархия классов со временем становится неуправляемой. Представьте, что в нашем приложении появилось десять гамбургеров и гарниров:

  • Для одной новой стороны потребовалось бы десять новых классов; один подкласс реализации для каждого существующего бургера
  • Новый бургер потребует одиннадцати классов; один для нового бургера и десять подклассов для каждой существующей стороны

Такое резкое увеличение количества классов быстро становится проблематичным, и именно эту проблему призван решить шаблон моста.

Расширение иерархии классов в коде

Теперь, когда мы поняли проблему, давайте рассмотрим пример кода, который демонстрирует проблему:

class AudioCommunicationsDevice {
  func sendAudio() {
  }
}

class CellPhone: AudioCommunicationsDevice {
  override func sendAudio() {
    // Implement function in subclasses
  }
}

class WalkieTalkie: AudioCommunicationsDevice {
  override func sendAudio() {
    // Implement function in subclasses
  }
}

CellPhone и WalkieTalkie соответствуют Hamburger и ChickenBurger из нашего предыдущего примера. Оба предназначены для разделения на подклассы для реализации функции отправки звука. Давайте добавим способы отправки звука и посмотрим, как расширяющаяся иерархия классов выглядит в коде.

class SecureCellPhone: CellPhone {
  override func sendAudio() {
    // Send encrypted audio
  }
}

class SecureWalkieTalkie: WalkieTalkie {
  override func sendAudio() {
    // Send encrypted audio
  }
}

class PlainAudioCellPhone: CellPhone {
  override func sendAudio() {
    // Send unencrypted audio
  }
}

class PlainAudioWalkieTalkie: WalkieTalkie {
  override func sendAudio() {
    // Send unencrypted audio
  }
}

Итак, в этом примере мы живем в гипотетическое время, когда устройства связи были способны отправлять только один тип звука. У нас есть устройства, которые отправляют простой звук, а некоторые устройства отправляют зашифрованный звук.

Вы видите здесь иерархию классов взрыва? Это тонко, но когда нам нужно было добавить новый способ отправки звука (например, зашифрованный звук), нам пришлось создать два дополнительных класса: SecureCellPhone и SecureWalkieTalkie.

Добавление нового устройства

Давайте добавим новое устройство и посмотрим, сколько еще классов добавлено.

// New Device
class LandlinePhone: AudioCommunicationsDevice {
  override func sendAudio() {
    // Implement function in subclasses
  }
}

// Audio Types
class SecureLandlinePhone: LandlinePhone {
  override func sendAudio() {
    // Send encrypted audio
  }
}

class PlainAudioLandlinePhone: LandlinePhone {
  override func sendAudio() {
    // Send plain audio
  }
}

Нам нужно было создать три новых класса, чтобы добавить одно новое устройство! Идеальным увеличением будет один класс на новое устройство или функцию обработки звука. Скорость занятий быстро увеличивается каждый раз, когда мы добавляем аудио-тип / устройство.

Иллюстрация решения

Теперь, когда у нас есть понимание того, как выглядит развивающаяся иерархия классов, давайте вернемся к нашему предыдущему примеру с едой. На рисунке ниже показано, как все будет выглядеть с примененным узором моста:

Две иерархии

Это нелогично, но решение избежать этого беспорядка - создать две отдельные иерархии. Вот что означает определение, когда оно гласит:

Шаблон Моста отделяет абстракцию от ее реализации ...

  1. «Абстракция» относится к базовому классу или протоколу. В наших примерах это относится к различным классам гамбургеров. Эти гамбургеры не предназначены для создания экземпляров, поскольку ресторан не продает их отдельно. Чтобы их можно было использовать, они должны быть разделены на подклассы с реализацией как полноценная еда.
  2. «Реализация» относится к классам блюд, которые мы готовим, когда добавляем гарниры к нашим гамбургерам.

Таким образом, мы отделяем наши гамбургеры (абстракция) от наших блюд / сторон (реализация) и остаемся с двумя иерархиями классов.

Наведение мостов между иерархиями

Далее в определении говорится:

Шаблон моста отделяет абстракцию от ее реализации, так что они могут различаться независимо.

Теперь, когда у нас есть две отдельные иерархии, нам нужен способ их соединения. Мы делаем это, задавая каждому Burger свойство типа Side. Это отношение «есть» является «мостом», соединяющим обе иерархии.

Все это мы делаем так, чтобы они могли различаться независимо друг от друга. Другими словами, нам больше не нужно создавать класс для каждой комбинации гамбургеров и гарниров. Поскольку их иерархии разделены, для каждого нового бургера или гарнира требуется только один новый класс.

Все это может показаться немного абстрактным, поэтому давайте вернемся к коду и продемонстрируем.

Кодирование решения

Нам нужна отдельная иерархия для устройств и аудио-типов. Начнем с аудио-типов:

protocol AudioHandling {
  func handle(audio: Audio) -> Audio
}

class AudioEncryptor: AudioHandling {
  func handle(audio: Audio) -> Audio {
    // Encrypt and return Audio
  }
}

class PlainAudioHandler: AudioHandling {
  func handle(audio: Audio) -> Audio {
    // return default audio
  }
}

Мы создаем протокол AudioHandling, и каждый соответствующий класс будет сосредоточен на подготовке звука для отправки устройством.

А теперь давайте заново реализуем наши устройства:

class CellPhone: AudioCommunicationsDevice {
  
  let audioHandler: AudioHandling
  
  init(audioHandler: AudioHandling) {
    super.init()
    
    self.audioHandler = audioHandler
  }
  
  override func sendAudio() {
    // Use audioHandler(audio:) to prepare audio, then send audio
  }
}

class WalkieTalkie: AudioCommunicationsDevice {
  
  let audioHandler: AudioHandling
  
  init(audioHandler: AudioHandling) {
    super.init()
    
    self.audioHandler = audioHandler
  }
  
  override func sendAudio() {
    // Use audioHandler(audio:) to prepare audio, then send audio
  }
}

Мы дали каждому устройству свойство audioHandler, которое позволило нашим устройствам «соединяться» и работать со всеми AudioHandling типами.

Вот обновленный визуальный элемент, отражающий наш код:

Мост в действии

Вот краткий пример использования паттерна моста:

let audioHandler = AudioEncryptor()
let walkieTalkie = WalkieTalkie(audioHandler: audioHandler)

walkieTalkie.sendAudio()

Наше применение моста довольно прямолинейно. Мы просто выбираем устройство для создания и передаем любой обработчик звука.

Вывод

Похлопайте себя по спине! Теперь вы знаете, как:

  1. Эффективно противодействовать растущей иерархии классов
  2. Постройте мост между двумя отдельными иерархиями

К вашему набору программирования добавлен еще один инструмент! Но мы пока не останавливаемся. Заходите на следующей неделе, чтобы увидеть еще один быстрый шаблон дизайна!

Первоначально опубликовано на сайте emanharout.com 29 июля 2017 г.