Абстрактная фабрика: искусство создания масштабируемого кода
Каждый разработчик рано или поздно сталкивается с моментом, когда стандартные решения перестают справляться с возросшими требованиями проекта. Именно в этот момент стоит рассмотреть паттерн «Абстрактная фабрика» — один из мощных инструментов, который помогает строить системы, готовые к расширениям и изменениям. Это не просто шаблон проектирования, это целая философия построения многогранного, но при этом структурированного кода.
Представим ситуацию: приложение должно поддерживать несколько тем оформления, работать с разными базами данных в зависимости от клиентского окружения или отправлять различные уведомления — «Абстрактная фабрика» позволяет элегантно и прозрачно решить эту задачу, позволяя интегрировать новшества без боли и страданий (хотя порой и кажется, что все мы здесь за этим собрались)
Чтобы показать работу паттерна давайте сделаем простую генерацию различных видов уведомлений в зависимости от выбранной фабрики (для отправки локальных уведомлений не забываем добавить разрешение в файл 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
}
Далее создаем реализацию уведомлений:
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])
}
}
На начальных этапах изучения складывается впечатление, что «Фабричный метод» и «Абстрактная фабрика» — это синонимы, однако это далеко не так. Различия между этими паттернами весьма значительны:
Абстрактная фабрика обычно используется для создания семейств взаимосвязанных или взаимозависимых объектов без спецификации их конкретных классов. В этом случае
NotificationFactory
является абстрактной фабрикой, которая создаёт семейство объектов, связанных с уведомлениями —Alert
иNotification
.Фабричный метод фокусируется на одном продукте и обычно включает один метод для создания объекта, а наследование используется для изменения типа создаваемого продукта. Если бы в вашем примере был один протокол с одним методом
create
, который возвращал бы разные типы объектов на основе какого-то параметра, и этот метод переопределялся в подклассах, то это был бы «Фабричный метод».
В вашем примере BasicNotificationFactory
предоставляет методы для создания двух разных типов продуктов: Alert
и Notification
. Каждый из этих продуктов может иметь множество вариаций (например, разные виды алертов и уведомлений), и фабрика может быть расширена другими фабриками для создания различных вариаций этих продуктов.
На этом на сегодня все, как сказал один класс к другому: «Я думал, мы можем быть друзьями, но ты постоянно создаешь что-то новое.»