Паттерны проектирования в Swift: от основ до практики
Паттерны проектирования (Design Patterns) — это проверенные решения типовых задач разработки. В мире Swift и экосистемы Apple (iOS/macOS) их знание становится обязательным для создания поддерживаемого, тестируемого и масштабируемого кода. В этой статье мы разберем ключевые паттерны, разделив их на три основные категории: порождающие, структурные и поведенческие. Каждый паттерн будет проиллюстрирован рабочим примером на Swift.
Порождающие паттерны: Singleton, Factory Method и Builder
Порождающие паттерны отвечают за процесс создания объектов, делая его более гибким и независимым от клиентского кода.
Singleton (Одиночка)
Гарантирует, что у класса есть только один экземпляр, и предоставляет глобальную точку доступа к нему. В iOS часто используется для управления сетевыми сессиями, базами данных (Core Data) или настройками приложения.
class NetworkManager {
static let shared = NetworkManager()
private init() {}
func fetchData(from url: URL) {
// Логика запроса
print("Fetching data from \(url)")
}
}
// Использование
NetworkManager.shared.fetchData(from: URL(string: "https://example.com")!)
Factory Method (Фабричный метод)
Определяет интерфейс для создания объекта, но позволяет подклассам изменять тип создаваемого объекта. Отлично подходит для создания UI-компонентов в зависимости от iOS-версии или конфигурации.
protocol Button {
func render()
}
class iOSButton: Button {
func render() { print("Rendering iOS button") }
}
class macOSButton: Button {
func render() { print("Rendering macOS button") }
}
enum Platform { case iOS, macOS }
class ButtonFactory {
static func createButton(for platform: Platform) -> Button {
switch platform {
case .iOS: return iOSButton()
case .macOS: return macOSButton()
}
}
}
// Использование
let button = ButtonFactory.createButton(for: .iOS)
button.render()
Builder (Строитель)
Позволяет пошагово создавать сложные объекты. Широко используется для конфигурации запросов (URLRequest) или сложных UI-настроек.
class Pizza {
var dough: String = ""
var cheese: String = ""
var toppings: [String] = []
}
class PizzaBuilder {
private var pizza = Pizza()
func setDough(_ dough: String) -> PizzaBuilder {
pizza.dough = dough
return self
}
func setCheese(_ cheese: String) -> PizzaBuilder {
pizza.cheese = cheese
return self
}
func addTopping(_ topping: String) -> PizzaBuilder {
pizza.toppings.append(topping)
return self
}
func build() -> Pizza {
return pizza
}
}
// Использование
let margherita = PizzaBuilder()
.setDough("Thin")
.setCheese("Mozzarella")
.addTopping("Basil")
.build()
Структурные паттерны: Adapter, Decorator и Facade
Структурные паттерны упрощают проектирование, определяя способы объединения объектов и классов в более крупные структуры.
Adapter (Адаптер)
Позволяет объектам с несовместимыми интерфейсами работать вместе. Классический пример — интеграция сторонних библиотек или работа с устаревшим кодом.
// Старый протокол
protocol OldPaymentSystem {
func pay(amount: Double)
}
// Новый протокол
protocol NewPaymentSystem {
func processPayment(amount: Double, currency: String)
}
// Адаптер
class PaymentAdapter: NewPaymentSystem {
private let oldSystem: OldPaymentSystem
init(oldSystem: OldPaymentSystem) {
self.oldSystem = oldSystem
}
func processPayment(amount: Double, currency: String) {
// Конвертация валюты
let convertedAmount = amount * 1.1
oldSystem.pay(amount: convertedAmount)
}
}
// Использование
let oldPayment = OldPaymentSystemImpl()
let adapter = PaymentAdapter(oldSystem: oldPayment)
adapter.processPayment(amount: 100, currency: "USD")
Decorator (Декоратор)
Динамически добавляет объекту новые обязанности. В iOS часто применяется для расширения функциональности UI-элементов или обработки данных.
protocol Coffee {
func cost() -> Double
func description() -> String
}
class SimpleCoffee: Coffee {
func cost() -> Double { return 2.0 }
func description() -> String { return "Simple coffee" }
}
class MilkDecorator: Coffee {
private let wrappedCoffee: Coffee
init(_ coffee: Coffee) {
self.wrappedCoffee = coffee
}
func cost() -> Double {
return wrappedCoffee.cost() + 0.5
}
func description() -> String {
return wrappedCoffee.description() + " + milk"
}
}
// Использование
var coffee: Coffee = SimpleCoffee()
coffee = MilkDecorator(coffee)
print("Cost: \(coffee.cost()), Description: \(coffee.description())")
Facade (Фасад)
Предоставляет простой интерфейс к сложной системе подсистем. Идеально подходит для инкапсуляции сложной логики работы с библиотеками (например, Core Location или AVFoundation).
class VideoConverterFacade {
private let codec = Codec()
private let compressor = Compressor()
private let fileManager = FileManager()
func convertAndSaveVideo(from source: URL, to destination: URL) {
let decodedData = codec.decode(source)
let compressedData = compressor.compress(decodedData)
fileManager.save(compressedData, to: destination)
print("Video converted and saved successfully")
}
}
// Использование
let converter = VideoConverterFacade()
converter.convertAndSaveVideo(from: sourceURL, to: destinationURL)
Поведенческие паттерны: Observer, Delegate и Strategy
Поведенческие паттерны определяют способы взаимодействия между объектами, распределяя обязанности и улучшая коммуникацию.
Observer (Наблюдатель)
Создает механизм подписки, позволяющий одним объектам следить за изменениями других. В Swift реализуется через Combine или NotificationCenter.
// Использование NotificationCenter (встроенный Observer)
extension Notification.Name {
static let userLoggedIn = Notification.Name("userLoggedIn")
}
class LoginService {
func loginUser() {
// Логика входа
NotificationCenter.default.post(name: .userLoggedIn, object: nil)
}
}
class ProfileViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(
self,
selector: #selector(updateUI),
name: .userLoggedIn,
object: nil
)
}
@objc func updateUI() {
print("UI updated after login")
}
deinit {
NotificationCenter.default.removeObserver(self)
}
}
Delegate (Делегат)
Блоги
Рекомендаций по книгам
Читайте также
Топ ошибок начинающих JavaScript разработчиков: как их избежать
Как удалить конкретный элемент из массива в JavaScript: 5 лучших способов
Поиск в Rust: находим индекс элемента массива с максимальной производительностью
10 лучших фреймворков Kotlin для веб-разработки — часть 3
Какие лучшие бесплатные курсы по Python
Блоги
Рекомендаций по книгам
Читайте также
Топ ошибок начинающих JavaScript разработчиков: как их избежать
10 лучших фреймворков Kotlin для веб-разработки — часть 3
Какие лучшие бесплатные курсы по Python