Тема довольно «заезженная», поэтому я буду часто ссылаться на другие статьи (здесь, например, можно прочитать про историю появления понятия Design Pattern и прочитать о нескольких из них). Моя же цель другая — undefinedуправлять поездомundefined структурировать собранную информацию и написать примеры под углом разработки на 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

Комментарии (2)


  1. LeshaRB
    17.10.2026 15:12

    А можно узнать смысл данной статьи

    Есть старая добрая книга банды четырех

    Ну или русского автора

    https://refactoring.guru/ru/design-patterns


    1. vagamagajaga Автор
      17.10.2026 15:12

      Попробуйте прочитать предисловие к статье ещё раз, там будет ответ на ваш вопрос

      P.S. Почти любая информация уже есть в сети, если для вас это статья тривиальна или избыточна - пройдите мимо. За ссылку спасибо, не знаком с этим ресурсом