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

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

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

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

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


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
}

Далее создаем реализацию уведомлений: реализуем два класса BasicAlert и LocalNotification, которые соответствуют протоколам Alert и Notification соответственно.

  • func send(): Метод для отправки уведомления, создает содержимое уведомления UNMutableNotificationContent с установленными заголовком и текстом, а также звуком по умолчанию. Затем создаем триггер UNTimeIntervalNotificationTrigger, который определяет, через сколько времени уведомление должно быть отправлено (в данном случае через 5 секунд без повторения) и UNNotificationRequest для запроса отправки уведомления. Наконец, добавляем этот запрос в UNUserNotificationCenter.

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)")
            }
        }
    }
}

Теперь создадим централизованное место для создания объектов уведомлений в приложении, которое позволяет легко заменять и модифицировать способ создания уведомлений, класс BasicNotificationFactory является реализацией протокола NotificationFactory. Он функционирует как конкретная фабрика, создающая объекты, которые соответствуют протоколам Alert и Notification

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)
    }
}

Переходим в наш контроллер, создаем кнопку, раскидываем якоря.

  • requestNotificationPermission(): Запрашивает разрешение пользователя на отправку уведомлений с опциями для баннера и звука. В случае успеха или ошибки выводит соответствующее сообщение в консоль.

  • showAlertButtonTapped(): Метод, который вызывается при нажатии на кнопку. Он создает и показывает предупреждение используя метод createAlert() фабрики factory и планирует локальное уведомление с помощью метода createNotification() той же фабрики.

Расширение (extension) ViewController: UNUserNotificationCenterDelegate реализует метод делегата userNotificationCenter(_:willPresent:withCompletionHandler:), который позволяет приложению отображать уведомления даже если оно находится на переднем плане. В нём вызывается completionHandler с опциями .banner, .list и .sound, которые определяют, как уведомление будет показано пользователю.

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. Каждый из этих продуктов может иметь множество вариаций (например, разные виды алертов и уведомлений), и фабрика может быть расширена другими фабриками для создания различных вариаций этих продуктов.

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

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