The Jerusalem PostIsraeli embassy temporarily closed as Albanian protesters clash with police in TiranaESPN DeportesBarcelona visita al Betis en busca de seguir invictoESPNHenderson, the Premier League's oldest player, is becoming undroppable for ChelseaPunchUse data to curb road crashes, Sultan tells FRSC marshalsBollywood HungamaYo Yo Honey Singh records statement in alleged obscene remarks case, questioned for over two hours: ReportsInquirerTalisay, Batangas heat index hits 40°C, precautions urgedRTP DesportoMiguel Oliveira abandona em Jerez em corrida ganha por BulegaZDF heuteEntdecken Sie das ZDF-NachrichtenstudioObservador DesportoMogadouro. Idoso morre após cair de telhadoDeadlinePro Wrestler Benjamin Satterley Cause Of Death DeterminedFootball ItaliaNicola ‘tired of rescue narrative, Napoli made it easier for Venezia’MintSeveral injured in stampede-like situation at Kolkata's Chorbagan Durga Puja pandal
The Daily Newsstand · Free, Always
Saturday, October 17, 2026

Паттерны проектирования. Порождающие

Translate

Тема довольно "заезженная", поэтому я буду часто ссылаться на другие статьи (здесь, например, можно прочитать про историю появления понятия Design Pattern и прочитать о нескольких из них). Моя же цель другая - управлять поездом структурировать собранную информацию и написать примеры под углом разработки на Swift.

Паттернами проектирования принято называть типовые решения архитектурных проблем, которые встречаются в процессе разработки. Их разделяют на 3 категории:

  • Порождающие (Creational)

  • Структурные (Structural)

  • Поведенческие (Behavioral)

В рамках небольшой серии статей я хочу поговорить о каждой категории отдельно. Начнем же с порождающих паттернов. Они отвечают за создание объектов и управление этим процессом.

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

1. Паттерн Фабрика

Основная идея паттерна - вынести логику создания конкретного объекта в отдельный компонент вместо вычислений на месте. Представим приложение-навигатор, которое поддерживает расчет времени от пункта А в пункт В в зависимости от выбранного транспорта.

protocol TransportService {
  func calculateTripDuration(_ way: Int) -> Int
}

class VehicleService: TransportService {
  func calculateTripDuration(_ way: Int) -> Int {
    // Логика расчета времени
  }
}

class BicycleService: TransportService {
  func calculateTripDuration(_ way: Int) -> Int {
    // Логика расчета времени
  }
}


class MapViewModel {
  var transportType: String = "Bycicle"
  var tripLength: Int
  ...
  func provideTripDuration() -> Int {
    if transportType == "Vehicle" {
      return VehicleService().calculateTripDuration(tripLength)
    } else {
      return BicycleService().calculateTripDuration(tripLength)
    }
  }
  ...
}

Фабрика берет логику выбора сервиса на себя, и при необходимости она также может конфигурировать создаваемый объект и передавать ему зависимости.

enum TransportType {
  case vehicle
  case bycicle
}

class TransportServiceFactory {
  func makeService(_ type: TransportType) -> TransportService {
    switch type {
      case .vehicle: return VehicleService()
      case .bycicle: return BicycleService()
    }
  }
}

Тогда в клиентском коде останется передать фабрике только тип транспорта.

class MapViewModel {
  var transportType: TransportType = .bycicle
  ...
  func provideTripDuration(type: TransportType) -> Int {
      let service = TransportServiceFactory().makeService(type)
      return service.calculateTripDuration(tripLength)
  }
}

Такой подход позволяет добавлять новые сервисы, не изменяя ViewModel. Для этого достаточно расширить enum и фабрику.

2. Абстрактная фабрика

Абстрактная фабрика полезна в ситуациях, когда необходимо скрыть внутренние детали создания объектов за публичным API. Этот паттерн также подходит для случаев, когда создание объекта представляет собой сложный процесс, требующий многократного повторения в разных частях приложения.

В C++, C# и Java существуют абстрактные классы, экземпляры которых нельзя создавать напрямую. Они могут определять общий интерфейс и содержать частичную реализацию, а также использоваться как основа для других классов. В Swift похожую задачу описания требований к типам решают протоколы. Именно их мы и будем использовать в дальнейшем.

В одной из статей (ссылка на нее ниже) автор назвал паттерн Абстрактная фабрика не иначе, как "Фабрика фабрик". Фактически, так и есть, мы создаем вложенные интерфейсы. В другой же статье мне понравилась структура, которая хорошо объясняет - что для реализации такого паттерна нужно. Ей и будем следовать.

  1. Абстрактный продукт

  2. Конкретный продукт

  3. Абстрактная фабрика

  4. Конкретная фабрика

  5. Клиент

Допустим, мы хотим описать отпуск в разных странах.

1. Абстрактные продукты - определим три протокола

protocol VacationActivityProtocol {
  // описывает вид отдыха
    func enjoy()
}

protocol TravelDocumentProtocol {
  // описывает необходимые документы
    func prepare()
}

protocol CommunicationLanguageProtocol {
  // описывает язык для общения
    func speak()
}

2. Конкретные продукты - создадим реализации для каждого направления

// MARK: - Thailand

struct BeachRelaxation: VacationActivityProtocol {
    func enjoy() {
        print("Sunbathing and swimming")
    }
}

struct InternationalPassport: TravelDocumentProtocol {
    func prepare() {
        print("Preparing an international passport")
    }
}

struct ThaiLanguage: CommunicationLanguageProtocol {
    func speak() {
        print("Speaking Thai")
    }
}

// MARK: - Armenia

struct MountainHiking: VacationActivityProtocol {
    func enjoy() {
        print("Hiking in the mountains")
    }
}

struct DomesticPassport: TravelDocumentProtocol {
    func prepare() {
        print("Preparing a domestic passport")
    }
}

struct ArmenianAndRussian: CommunicationLanguageProtocol {
    func speak() {
        print("Speaking Armenian or Russian")
    }
}

3. Абстрактная фабрика - определим протокол фабрики, которая создаёт все три продукта

protocol VacationFactoryProtocol {
    func makeActivity() -> VacationActivityProtocol
    func makeDocument() -> TravelDocumentProtocol
    func makeLanguage() -> CommunicationLanguageProtocol
}

4. Конкретная фабрика - реализуем фабрики для двух направлений

struct ThailandVacationFactory: VacationFactoryProtocol {
    func makeActivity() -> VacationActivityProtocol {
        BeachRelaxation()
    }

    func makeDocument() -> TravelDocumentProtocol {
        InternationalPassport()
    }

    func makeLanguage() -> CommunicationLanguageProtocol {
        ThaiLanguage()
    }
}

struct ArmeniaVacationFactory: VacationFactoryProtocol {
    func makeActivity() -> VacationActivityProtocol {
        MountainHiking()
    }

    func makeDocument() -> TravelDocumentProtocol {
        DomesticPassport()
    }

    func makeLanguage() -> CommunicationLanguageProtocol {
        ArmenianAndRussian()
    }
}

Каждая фабрика создаёт семейство продуктов, соответствующее выбранному направлению отпуска.

5. Клиент - создадим класс, который планирует отпуск, используя только абстрактную фабрику

final class VacationPlanner {
    private let factory: VacationFactoryProtocol

    init(factory: VacationFactoryProtocol) {
        self.factory = factory
    }

    func planVacation() {
        let activity = factory.makeActivity()
        let document = factory.makeDocument()
        let language = factory.makeLanguage()

        activity.enjoy()
        document.prepare()
        language.speak()
    }
}

Теперь наш класс по организации отпуска готов.

let thailandVacation = VacationPlanner(
    factory: ThailandVacationFactory()
)
thailandVacation.planVacation()

// Sunbathing and swimming
// Preparing an international passport
// Speaking Thai

Абстрактная фабрика позволяет создавать семейства связанных объектов, не привязывая клиентский код к конкретным реализациям. В нашем случае VacationPlanner не нужно изменять, чтобы переключиться с Таиланда на Армению. Достаточно передать другую фабрику.

3. Паттерн Строитель (Builder)

Основная идея шаблона Строитель в возможности пошагово настраивать параметры создаваемого объекта. Одним из популярных примеров из SDK Foundation можно назвать сборку параметров URLRequest'a перед тем, как положить его в URLSession.

Для наглядности возьмем более общий пример - настройка анимационного аватара в приложении. Сначала перечислим некоторые параметры и создадим саму модель:

// Типы параметры
enum HairColor {
  case blond
  case brunette
  case ginger
}

enum EyesColor {
  case green
  case blue
  case hazel
}

// Модель
struct Avatar {
  let hair: HairColor
  let eyes: EyesColor
  let hasGlasses: Bool
}

А теперь создадим объект, создающий нашу модель согласно паттерну:

final class AvatarBuilder {
  private var hair: HairColor = .blond
  private var eyes: EyesColor = .green
  private var hasGlasses: Bool = true

  func with(hair: HairColor) -> AvatarBuilder {
    self.hair = hair      
    return self
  }
    
  func with(eyes: EyesColor) -> AvatarBuilder {
    self.eyes = eyes
    return self
  }

  func with(glasses: Bool) -> AvatarBuilder {
    self.hasGlasses = glasses
    return self
  }
    
  func build() -> Avatar {
    Avatar(
      hair: hair,
      eyes: eyes,
      hasGlasses: hasGlasses
    )
  }
}

И сам способ сборки в коде:

let avatar = AvatarBuilder()
    .with(hair: .ginger)
    .with(eyes: .hazel)
    .with(glasses: true)
    .build()

Обычно шаблон встречается там, где есть большое количество параметров, зачастую опциональных. Если речь о модели с небольшым количеством свойств, то использование паттерна будет избыточным.

Этот подход напоминает работу с модификаторами в SwiftUI, с помощью которых настраиваются UI-элементы. В статье ниже можно найти пример применения паттерна Builder для создания и настройки UI-компонента.

4. Паттерн Прототип

Не уходя далеко от нашего примера с аватаром, я предлагаю задуматься над таким вопросом: "А могу ли я не создавать объект с нуля, а копировать его с небольшими изменениями?". Как раз эту задачу и описывает паттерн Прототип - он как бы берет за основу оригинальный объект (прототип) и создает его копию с необходимыми изменениями.

Надо сказать, что для работы со структурами и классами (основные объекты в Swift) этот шаблон будет иметь свои нюансы. Начнем со структуры.

struct Avatar {
  let hair: HairColor
  let eyes: EyesColor
  let hasGlasses: Bool

  func copy(
    hair: HairColor? = nil,
    eyes: EyesColor? = nil,
    hasGlasses: Bool? = nil
  ) -> Avatar {
    Avatar(
      hair = hair ?? self.hair,
      eyes = eyes ?? self.eyes,
      hasGlasses = hasGlasses ?? self.hasGlasses
    )
  }
}

И использование такого подхода на месте будет выглядеть следующим образом:

let original = Avatar(hair: .blond, eyes: .blue, hasGlasses: true)
let copy = original.copy(hasGlasses: false)

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

let original = Avatar(hair: .blond, eyes: .blue, hasGlasses: true)
var copy = original
copy.hasGlasse = false

Да, но все же первый вариант кажется "чище", учитывая рекомендации по использованию констант в моделях.

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

final class Avatar {
  let hair: HairColor
  let eyes: EyesColor
  let hasGlasses: Bool

  init(...) {
    ...
  }

  func copy(
    hair: HairColor? = nil,
    eyes: EyesColor? = nil,
    hasGlasses: Bool? = nil
  ) -> Self {
    Avatar(
      hair = hair ?? self.hair,
      eyes = eyes ?? self.eyes,
      hasGlasses = hasGlasses ?? self.hasGlasses
    )
  }
}

let original = Avatar(hair: .blond, eyes: .blue, hasGlasses: true)
let copy = original.copy(hasGlasses: false)

______________________________________________________________________

final class Avatar {
  var hair: HairColor
  var eyes: EyesColor
  var hasGlasses: Bool

  init(...) {
    ...
  }
  
let original = Avatar(hair: .blond, eyes: .blue, hasGlasses: true)
var copy = original
copy.hasGlasse = false

В случае с классами ценность паттерна Prototype становится особенно очевидной. Поскольку классы в Swift обладают ссылочной семантикой, присваивание одного экземпляра другому не создаёт копию объекта: обе переменные будут ссылаться на один и тот же экземпляр. Поэтому изменение его свойств через одну переменную отразится и при обращении через другую.

В первом примере мы реализуем метод copy(), который создаёт новый экземпляр на основе существующего, позволяя при необходимости изменить отдельные свойства. В результате оригинал и копия становятся независимыми объектами.

Отмечу, что в Foundation существует протокол NSCopying, который позволяет определить механизм копирования экземпляров классов. При этом сам протокол не реализует копирование автоматически — разработчик должен описать его логику.

5. Синглтон

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

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

final class Logger {

  static shared: Logger = Logger()
  
  private init() {}

  func log(_ message: String) {
        print("[LOG]: \(message)")
    }
}

Чтобы воспользоваться функциональностью Logger, достаточно обратиться к его единственному экземпляру через свойство shared.

func setAvatar {
  Logger.shared.log("User avatar is set.")
}

Заключение

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

В следующей статье поговорим о Структурных паттернах.

Материалы

Паттерн Фабрика
Абстрактная Фабрика
Abstract Factory
Builder
Одиночкa

View the original on Хабр →

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.