Абстрактная фабрика: искусство создания масштабируемого кода

a9102f698b0ec6db750b575c0011e443.png

Каждый разработчик рано или поздно сталкивается с моментом, когда стандартные решения перестают справляться с возросшими требованиями проекта. Именно в этот момент стоит рассмотреть паттерн «Абстрактная фабрика» — один из мощных инструментов, который помогает строить системы, готовые к расширениям и изменениям. Это не просто шаблон проектирования, это целая философия построения многогранного, но при этом структурированного кода.

Представим ситуацию: приложение должно поддерживать несколько тем оформления, работать с разными базами данных в зависимости от клиентского окружения или отправлять различные уведомления — «Абстрактная фабрика» позволяет элегантно и прозрачно решить эту задачу, позволяя интегрировать новшества без боли и страданий (хотя порой и кажется, что все мы здесь за этим собрались)

Чтобы показать работу паттерна давайте сделаем простую генерацию различных видов уведомлений в зависимости от выбранной фабрики (для отправки локальных уведомлений не забываем добавить разрешение в файл Info.plist)

Что вообще хотим увидеть:

7e1181e4e3d9800adb34fdf1031b33a3.jpg

Прежде всего начнем с протоколов для определения интерфейсов уведомлений и фабрики для создания уведомлений:


protocol Alert {
    func show(in viewController: UIViewController)
}

protocol Notification {
    func send()
}

protocol NotificationFactory {
    func createAlert(title: String, message: String) -> Alert
    func createNotification(title: String, body: String) -> Notification
}

Далее создаем реализацию уведомлений:

import UIKit
import UserNotifications

class BasicAlert: Alert {
    private var title: String
    private var message: String

    init(title: String, message: String) {
        self.title = title
        self.message = message
    }

    func show(in viewController: UIViewController) {
        let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
        alertController.addAction(UIAlertAction(title: "OK", style: .default))
        viewController.present(alertController, animated: true)
    }
}


class LocalNotification: Notification {
    private var title: String
    private var body: String

    init(title: String, body: String) {
        self.title = title
        self.body = body
    }

    func send() {
        let content = UNMutableNotificationContent()
        content.title = title
        content.body = body
        content.sound = .default

        let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5, repeats: false)
        let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger)

        UNUserNotificationCenter.current().add(request) { error in
            if let error = error {
                print("Ошибка при добавлении локального уведомления: \(error)")
            }
        }
    }
}

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

class BasicNotificationFactory: NotificationFactory {

    func createAlert(title: String, message: String) -> Alert {
        return BasicAlert(title: title, message: message)
    }

    func createNotification(title: String, body: String) -> Notification {
        return LocalNotification(title: title, body: body)
    }
}

Переходим в наш контроллер:

import UIKit

class ViewController: UIViewController {

    var factory: NotificationFactory = BasicNotificationFactory()
    
    lazy var alertButton: UIButton = {
        let button = UIButton()
        button.setTitle("Показать уведомление", for: .normal)
        button.setTitleColor(.systemBlue, for: .normal)
        button.addTarget(self, action: #selector(showAlertButtonTapped), for: .touchUpInside)
        return button
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        setupViews()
        setupConstraints()
        requestNotificationPermission()
        UNUserNotificationCenter.current().delegate = self
    }
    
    private func setupViews() {
        view.backgroundColor = .white
        view.addSubview(alertButton)
    }
    
    private func setupConstraints() {
        alertButton.translatesAutoresizingMaskIntoConstraints = false
        alertButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        alertButton.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
    }

  //выполняем запрос разрешений на отправку локальных уведомлений пользователю
    private func requestNotificationPermission() {
        UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound]) { granted, error in
            if granted {
                print("Разрешение на отправку уведомлений получено.")
            } else if let error = error {
                print("Ошибка при запросе разрешения на отправку уведомлений: \(error)")
            }
        }
    }

    @objc func showAlertButtonTapped() {
        let alert = factory.createAlert(title: "Внимание", message: "Это пример абстрактной фабрики.")
        alert.show(in: self)
        
        let notification = factory.createNotification(title: "Привет", body: "Это локальное уведомление.")
        notification.send()
    }
}

extension ViewController: UNUserNotificationCenterDelegate {
    func userNotificationCenter(_ center: UNUserNotificationCenter,
                                willPresent notification: UNNotification,
                                withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        //показываем уведомление даже когда приложение на переднем плане
        completionHandler([.banner, .list, .sound])
    }
}

На начальных этапах изучения складывается впечатление, что «Фабричный метод» и «Абстрактная фабрика» — это синонимы, однако это далеко не так. Различия между этими паттернами весьма значительны:

  1. Абстрактная фабрика обычно используется для создания семейств взаимосвязанных или взаимозависимых объектов без спецификации их конкретных классов. В этом случае NotificationFactory является абстрактной фабрикой, которая создаёт семейство объектов, связанных с уведомлениями — Alert и Notification.

  2. Фабричный метод фокусируется на одном продукте и обычно включает один метод для создания объекта, а наследование используется для изменения типа создаваемого продукта. Если бы в вашем примере был один протокол с одним методом create, который возвращал бы разные типы объектов на основе какого-то параметра, и этот метод переопределялся в подклассах, то это был бы «Фабричный метод».

В вашем примере BasicNotificationFactory предоставляет методы для создания двух разных типов продуктов: Alert и Notification. Каждый из этих продуктов может иметь множество вариаций (например, разные виды алертов и уведомлений), и фабрика может быть расширена другими фабриками для создания различных вариаций этих продуктов.

На этом на сегодня все, как сказал один класс к другому: «Я думал, мы можем быть друзьями, но ты постоянно создаешь что-то новое.»

© Habrahabr.ru