Какие паттерны проектирования вы знаете и применяли на практике?

Ответ

Паттерны проектирования — это типовые решения распространенных проблем. Я разделяю их на три классические категории и привожу примеры использования в iOS/macOS разработке.

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

Управляют процессом создания объектов.

  • Singleton (Одиночка): Гарантирует наличие единственного экземпляра класса. Используется осторожно, преимущественно для stateless-менеджеров.
    class AppSettings {
        static let shared = AppSettings()
        private init() {}
        var isDarkMode: Bool = false
    }
  • Factory Method (Фабричный метод): Определяет интерфейс для создания объекта, но позволяет подклассам изменять тип создаваемого объекта.
  • Builder (Строитель): Позволяет создавать сложные объекты пошагово, отделяя конструирование от представления.

2. Структурные (Structural)

Определяют, как комбинировать классы и объекты в более крупные структуры.

  • Adapter (Адаптер): Позволяет объектам с несовместимыми интерфейсами работать вместе. Часто используется при интеграции сторонних сервисов.
  • Decorator (Декоратор): Динамически добавляет объекту новые обязанности. В Swift реализуется через расширения (extensions) и композицию.
  • Facade (Фасад): Представляет простой интерфейс к сложной подсистеме (например, фасад для сетевого слоя).

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

Решают задачи эффективного взаимодействия объектов и распределения ответственности.

  • Observer (Наблюдатель): Позволяет объектам уведомлять другие объекты об изменениях своего состояния. Реализации: NotificationCenter, KVO, @Published в Combine.
  • Delegate (Делегат): Объект поручает другому объекту (делегату) выполнение определенных задач. Краеугольный камень многих Cocoa Touch API (UITableViewDelegate).
  • Strategy (Стратегия): Определяет семейство алгоритмов, инкапсулирует каждый из них и делает их взаимозаменяемыми. Позволяет изменять алгоритм независимо от клиента, который его использует.
    protocol SortingStrategy {
        func sort<T: Comparable>(_ array: [T]) -> [T]
    }
    class BubbleSortStrategy: SortingStrategy { /* ... */ }
    class QuickSortStrategy: SortingStrategy { /* ... */ }
    class DataProcessor {
        private var sorter: SortingStrategy
        init(sorter: SortingStrategy) { self.sorter = sorter }
        func process(data: [Int]) -> [Int] { return sorter.sort(data) }
    }

В iOS-разработке также широко применяются архитектурные паттерны (MVC, MVVM, VIPER), которые организуют код на уровне всего модуля или приложения.

Ответ 18+ 🔞

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

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

Эти ребята отвечают за то, как объекты на свет появляются, чтобы не было, как в плохом борделе, — кто во что горазд.

  • Singleton (Одиночка): Гарантирует, что у класса будет только один-единственный экземпляр на весь проект. Используй осторожно, а то превратишь всё в глобальное болото. Идеален для всяких менеджеров без состояния.
    class AppSettings {
        static let shared = AppSettings()
        private init() {} // Вот этот приватный инит — главный страж, чтобы второй такой же мудак не создался
        var isDarkMode: Bool = false
    }
    // Используется: AppSettings.shared.isDarkMode = true
  • Factory Method (Фабричный метод): Суть в том, что ты говоришь: «Создай мне что-то!», а система уже сама решает, какую конкретную хуйню (простите, реализацию) тебе подсунуть. Отличный способ не завязываться на конкретные классы.
  • Builder (Строитель): Когда объект с кучей полей, и половина из них опциональные. Вместо инита на двадцать параметров ты по кирпичику его собираешь. Красота, а не жизнь.

2. Структурные (Structural)

Тут речь о том, как склеить разные объекты в одну большую и рабочую конструкцию, чтобы она не развалилась при первом же чихе.

  • Adapter (Адаптер): Представь, ты купил новую библиотеку, а её интерфейс не совместим с твоим старым кодом. Вместо того чтобы переписывать всё к хуям, ты пишешь адаптер — прослойку, которая переводит с твоего языка на язык библиотеки. Как переводчик-синхронист, только для кода.
  • Decorator (Декоратор): Нужно добавить функциональность объекту, но не хочется лезть в его родной класс или плодить тонны подклассов. В Swift это делается через extensions или просто композицию — оборачиваешь объект в другой, который добавляет нужные фичи. Гибко, как мартышлюшка.
  • Facade (Фасад): У тебя есть целая подсистема — сетевой слой, например, с кучей классов: парсер, запрос, кеш, валидатор. Фасад — это такая красивая дверца с одной кнопкой fetchData(), за которой скрывается вся эта ебопота. Клиенту — просто, тебе — порядок.

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

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

  • Observer (Наблюдатель): Объект А кричит: «Эй, я изменился!». А объекты Б, В и Г ему: «О, спасибо, мы заметили!». Реализаций в iOS — овердохуища: старый добрый NotificationCenter, KVO (который лучше обходить стороной), модный @Published в Combine.
  • Delegate (Делегат): Краеугольный камень, блядь, всей iOS-разработки. Один объект (например, UITableView) говорит: «Я не буду сам решать, что делать при тапе на ячейку. Вот тебе, другой объект, протокол. Реализуй его методы и делай там что хочешь». Всё строится на этом соглашении. Без делегатов — нихуя не работает.
  • Strategy (Стратегия): Суперполезная штука. Допустим, у тебя есть алгоритм сортировки. Вместо того чтобы городить if-else или switch внутри класса, ты выносишь каждый алгоритм в отдельный объект-стратегию. И подменяешь их на лету. Главное — чтобы интерфейс у всех стратегий был одинаковый.
    protocol SortingStrategy {
        func sort<T: Comparable>(_ array: [T]) -> [T]
    }
    class BubbleSortStrategy: SortingStrategy { /* ... */ }
    class QuickSortStrategy: SortingStrategy { /* ... */ }
    class DataProcessor {
        private var sorter: SortingStrategy // Храним стратегию
        init(sorter: SortingStrategy) { self.sorter = sorter }
        func process(data: [Int]) -> [Int] { return sorter.sort(data) } // Используем её
    }
    // Сегодня быстрая сортировка, завтра — пузырьковая. Клиенту похуй, главное — результат.

Ну и конечно, поверх этого всего стоят архитектурные паттерны вроде MVC, MVVM или VIPER. Это уже не про мелкие детали, а про то, как организовать весь твой модуль или приложение, чтобы через полгода ты сам мог в нём разобраться, а не хотел вырвать глаза со словами «какой пидарас это писал?». Оказалось, что ты и есть тот пидарас, Колян. Вот так-то.