Pull to refresh

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

Level of difficultyMedium
Reading time6 min
Views12K

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

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

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

Для начала объявим протоколы которые определяют интерфейсы для объектов оповещения и уведомлений:

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

protocol Notification {
    func send()
}

Далее определяем протокол NotificationFactory, который представляет собой абстрактную фабрику для создания объектов Alert и Notification:

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

Теперь создадим класс BasicNotificationFactory, который реализует протокол NotificationFactory и предоставляет конкретные реализации для создания базовых уведомлений и оповещений. Аналогично создаем класс AdvancedNotificationFactory, но предоставляющий более продвинутые версии уведомлений и оповещений:

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


class AdvancedNotificationFactory: NotificationFactory {
    func createAlert(title: String, message: String) -> Alert {
        return AdvancedAlert(title: title, message: message)
    }

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

Теперь объявим протокол FactoryCreator, который определяет интерфейс для создания объектов типа NotificationFactory. Это "фабрика фабрик".

protocol FactoryCreator {
    func createFactory() -> NotificationFactory
}

Далее создаем класс BasicFactoryCreator, реализующий протокол FactoryCreator, предоставляет метод для создания экземпляра BasicNotificationFactory и класс AdvancedFactoryCreator, аналогичный BasicFactoryCreator, но создаёт экземпляр AdvancedNotificationFactory:

class BasicFactoryCreator: FactoryCreator {
    func createFactory() -> NotificationFactory {
        return BasicNotificationFactory()
    }
}


class AdvancedFactoryCreator: FactoryCreator {
    func createFactory() -> NotificationFactory {
        return AdvancedNotificationFactory()
    }
}

Мы определили семейство связанных или зависимых объектов без указания конкретных классов. FactoryCreator позволяет переключаться между разными "семействами" объектов (базовыми и продвинутыми версиями уведомлений и оповещений), а NotificationFactory отвечает за создание этих объектов.


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

class NotificationService {
    
    var factory: NotificationFactory = BasicNotificationFactory()

    func requestNotificationPermission(completion: @escaping (Bool) -> Void) {
        UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound]) { granted, error in
            if let error = error {
                print("Error requesting permission to send notifications: \(error)")
            }
            completion(granted)
        }
    }

    func createAndShowAlert(in viewController: UIViewController, title: String, message: String) {
        let alert = factory.createAlert(title: title, message: message)
        alert.show(in: viewController)
    }

    func sendNotification(title: String, body: String) {
        let notification = factory.createNotification(title: title, body: body)
        notification.send()
    }

    func switchFactory(to newFactory: NotificationFactory) {
        factory = newFactory
    }
}
  • объявляем свойство factory, которое является типом NotificationFactory. По умолчанию оно инициализируется как BasicNotificationFactory. Это означает, что изначально используются базовые реализации уведомлений и оповещений.

  • объявляем метод для запроса разрешения на отправку уведомлений requestNotificationPermission()

  • внутри метода requestNotificationPermission, используем UNUserNotificationCenter для запроса разрешения на показ уведомлений с опциями .alert и .sound. В замыкании обрабатываем результат запроса

  • createAndShowAlert(): метод для создания и отображения оповещения, использует factory для создания оповещения (Alert) и затем отображает его в переданном контроллере

  • sendNotification(): метод для отправки уведомления. Аналогично предыдущему методу использует factory для создания объекта Notification и затем отправляет уведомление

  • switchFactory(): метод для изменения фабрики уведомлений. Это позволяет переключаться между различными реализациями фабрик (например, между базовой и продвинутой), изменяя способ создания уведомлений и оповещений


Наконец переходим в контроллер:

import UIKit

class ViewController: UIViewController {
    
    private let notificationService = NotificationService()

    lazy var alertButton: UIButton = {
        let button = UIButton()
        button.setTitle("Show Notification", for: .normal)
        button.setTitleColor(.systemBlue, for: .normal)
        button.addTarget(self, action: #selector(showAlertButtonTapped), for: .touchUpInside)
        return button
    }()

    lazy var toggleFactoryButton: UIButton = {
        let button = UIButton()
        button.setTitle("Switch to Advanced Factory", for: .normal)
        button.setTitleColor(.systemBlue, for: .normal)
        button.addTarget(self, action: #selector(toggleFactory), for: .touchUpInside)
        return button
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        setupViews()
        setupConstraints()
        notificationService.requestNotificationPermission { granted in
            if granted {
                print("Уведомления разрешены.")
                // Здесь можно запланировать какие-то уведомления или выполнить другие действия, требующие разрешения на уведомления.
            } else {
                print("Уведомления не разрешены.")
                // Здесь можно обработать ситуацию, когда пользователь отказал в разрешении.
            }
        }
        UNUserNotificationCenter.current().delegate = self
    }

    private func setupViews() {
        view.backgroundColor = .white
        view.addSubview(alertButton)
        view.addSubview(toggleFactoryButton)
    }

    private func setupConstraints() {
        alertButton.translatesAutoresizingMaskIntoConstraints = false
        alertButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        alertButton.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true

        toggleFactoryButton.translatesAutoresizingMaskIntoConstraints = false
        toggleFactoryButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        toggleFactoryButton.topAnchor.constraint(equalTo: alertButton.bottomAnchor, constant: 20).isActive = true
    }

    @objc func showAlertButtonTapped() {
        notificationService.createAndShowAlert(in: self, title: "Attention", message: "This is an example of an abstract factory.")
        notificationService.sendNotification(title: "Hello", body: "This is a local notification.")
    }

    @objc func toggleFactory() {
        if let _ = notificationService.factory as? BasicNotificationFactory {
            notificationService.switchFactory(to: AdvancedNotificationFactory())
            toggleFactoryButton.setTitle("Switch to Basic Factory", for: .normal)
        } else {
            notificationService.switchFactory(to: BasicNotificationFactory())
            toggleFactoryButton.setTitle("Switch to Advanced Factory", for: .normal)
        }
    }
}

extension ViewController: UNUserNotificationCenterDelegate {
    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        
        completionHandler([.banner, .list, .sound])
    }
}
  • private let notificationService = NotificationService(): создаем экземпляр нашего сервиса, который будет использоваться для управления уведомлениями

  • накидываем UI и раскидываем якоря

  • showAlertButtonTapped(): создает и показывает оповещение, а также отправляет уведомление

  • toggleFactory(): переключает между базовой и продвинутой фабриками уведомлений и изменяет текст кнопки

  • extension ViewController: UNUserNotificationCenterDelegate: определяет поведение при получении уведомлений, когда приложение активно

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

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

Tags:
Hubs:
Total votes 15: ↑7 and ↓80
Comments8

Articles