Шаблони в iOS

У цій статті ми розглянемо шаблони iOS. Деякі з них будуть розглянуті детально, інші будуть коротко описані, а деякі взагалі не будуть включені.

Що таке шаблон дизайну? Це повторюване рішення для загальних проблем у розробці додатків. Шаблони допомагають розробникам писати зрозумілий і простий у використанні код.

Вимоги: час і розум :)

Усі шаблони можна поділити на три категорії: створювальні, структурні та поведінкові.

Сінглтон (категорія створювальних шаблонів)

Уявіть ситуацію, коли вам потрібна лише одна копія об'єкта. Наприклад, це може бути реальний об'єкт: принтер, сервер або щось, що не повинно мати кілька копій. Наприклад, iOS пристрої мають об'єкт UserDefaults, до якого можна звертатися через властивість standard. Ось приклад його реалізації:

private override init(){}static let sharedInstance = EventManager()
    override func copy()-> Any {
        fatalError("Використання методу copy для сінглтона заборонено!")}
    override func mutableCopy()-> Any {
        fatalError("Використання методу mutableCopy для сінглтона заборонено!")}

Для класу EventManager я створив private ініціалізатор, щоб ніхто не міг створити нову копію об'єкта. Я також додав статичну змінну sharedInstance і ініціалізував її об'єктом EventManager(). Потім, щоб уникнути копіювання об'єкта, я переписав методи copy() і mutableCopy(). У ранніх версіях Swift ви могли використовувати dispatch_once{}, але в Swift 3.0 ця функція більше не доступна.

Фабричний метод (категорія створювальних шаблонів)

Фабричний метод використовується, коли необхідно зробити вибір між класами, які реалізують спільний протокол або мають спільний базовий клас. Цей шаблон містить логіку, яка вирішує, який клас вибрати.

Вся логіка, як і в назві шаблону, знаходиться в методі, який інкапсулює рішення.

У нас є два варіанти реалізації: використати глобальний метод або базовий клас.

Глобальний метод:

func createRentalCar(_ passengers:Int)-> RentalCar? {
        var carImp: RentalCar.Type?
        switch passengers {case0...3: carImp = Compact.self
        case4...8: carImp = SUV.self
        default: carImp =nil}return carImp?.createRentalCar(passengers)}

Використання базового класу передбачає перенесення цієї логіки до базового класу.

Абстрактна фабрика (категорія створювальних шаблонів)

Шаблон абстрактної фабрики дуже схожий на описаний вище, за винятком того, що він використовується для створення групи об'єктів. Шаблон не може передбачити, яка реалізація буде використана, але допомагає вибрати відповідний конкретний об'єкт.

//серце шаблону
    final class func getFactory(car: Cars)-> CarFactory? {
        var factory: CarFactory?
        switch car {case .compact: factory = CompactCarFactory()case .sports: factory = SportsCarFactory()case .SUV: factory = SUVCarFactory()}return factory
    }

Цей метод розташований у базовому абстрактному класі. Ми можемо використовувати його у структурі Car:

struct Car {
    var carType: Cars
    var floor: FloorPlan
    var suspension: Suspension
    var drive: Drivetrain
 
    init(carType: Cars){
        let concreteFactory = CarFactory.getFactory(car: carType)
        self.floor= concreteFactory!.createFloorplan()
        self.suspension = concreteFactory!.createSuspension()
        self.drive = concreteFactory!.createDrivetrain()
        self.carType = carType
    }
 
    func printDetails(){
        print("Тип автомобіля: \(carType.rawValue)")
        print("Місць: \(floor.seats)")
        print("Двигун: \(floor.enginePosition.rawValue)")
        print("Підвіска: \(suspension.suspensionType.rawValue)")
        print("Привід: \(drive.driveType.rawValue)")}}

Будівельник (категорія створення)

Будівельник — це шаблон дизайну для iOS, що використовується для розділення конфігурації та створення об'єкта. Викликаючий об'єкт містить дані конфігурації та передає їх об'єкту Будівельника, який відповідає за створення об'єкта. Уявімо, що у нас є ресторан, і нам потрібно створити застосунок для замовлення бургерів:

let builder = BurgerBuilder()
 
//1 етап
let name ="Joe"
 
//2 етап
builder.setVeggie(choice:false)
 
//3 етап
builder.setMayo(choice:false)
builder.setCooked(choice: .welldone)
 
//4 етап
builder.addPatty(choice:true)
 
let order = builder.buildObject(name: name)
order.printDescription()

Щоб уникнути опитування відвідувачів щодо кожного інгредієнта для їх бургерів, ми створимо значення за замовчуванням у класі Будівельника.

…
 private var veggie =false
    private var pickles =false
    private var mayo =true
    private var ketchup =true
    private var lettuce =true
    private var cooked = Burger.Cooked.normal
    private var patties =2
    private var bacon =true

Якщо виявиться, що клієнти хочуть додати майонез, тоді ми змінимо значення за замовчуванням на true у класі Будівельника. Більше того, час опитування клієнтів залишиться незмінним.

MVC (категорія структури)

MVC — це ще один шаблон для iOS. MVC розшифровується як Модель-Погляд-Контролер. Модель працює лише з даними (модель). Погляд працює з усім, що передбачає малювання елементів інтерфейсу та анімацію різних кнопок. Однак краще використовувати окремий клас для анімації: ви можете захотіти щось змінити пізніше). Нарешті, контролер «збирає» все це разом.

З часом контролер стане більш складним через логіку, пов'язану з моделлю. Тому вам доведеться використовувати більш просунутий шаблон під назвою модель-погляд-модельПогляду (MVVM). Дізнайтеся більше про шаблон MVVM за посиланням.

Фасад (категорія структури)

Фасад — це ще один представник шаблонів дизайну застосунків для iOS. Він пропонує один спрощений інтерфейс для складних систем. Замість того, щоб показувати численні методи з різними інтерфейсами, ми повинні створити свій власний клас, інкапсулюючи в ньому інші об'єкти, щоб надати користувачеві більш спрощений інтерфейс.

Цей шаблон корисний, коли вам потрібно замінити, наприклад, Alamofire на NSURLSession. Ви повинні внести зміни лише у своєму класі Facade, не редагуючи його інтерфейс.

Приклад інтерфейсу з класом SocketManager:

final internal class SocketManager :NSObject{
 
    static internal let sharedInstance: SocketManager.SocketManager
 
    override internal func copy()-> Any
 
    override internal func mutableCopy()-> Any
 
    internal var connectionWasOpened: Bool
 
    internal var lastUserId: String
 
    internal func openNewConnection(userId: String)
 
    internal func closeConnection()
 
    internal func logoutFromWebSocket()
 
    internal func reconnect()}

Кінцевий користувач може не знати, що ми використовуємо SocketRocket. Пізніше я можу замінити його на щось інше, і мені не потрібно буде вносити зміни в усі місця, де він використовувався. Достатньо буде редагувати лише один клас.

Декоратор (структурна категорія)

Серед шаблонів iOS-додатків також є той, що називається Декоратор. Він додає необхідну поведінку та відповідальність до об'єкта, не модифікуючи його код. Це може бути корисним, коли, наприклад, ми використовуємо сторонні бібліотеки і не маємо доступу до вихідного коду.

У Swift є два поширених варіанти реалізації цього шаблону: розширення і делегування.

Адаптер (структурна категорія)

Адаптер дозволяє класам з несумісними інтерфейсами працювати разом. Apple реалізує цей шаблон за допомогою протоколів. Адаптер використовується, коли вам потрібно інтегрувати компонент, код якого не може бути змінений. Цей шаблон корисний, коли ми маємо справу з застарілим продуктом. Оскільки ми не знаємо, як він працює, ми уникаємо його модифікації.

Міст (структурна категорія)

Міст є ще одним представником шаблонів дизайну iOS-додатків. Він дуже схожий на Адаптер, але має кілька відмінностей. У випадку з Мостом ми можемо модифікувати вихідний код, оскільки маємо до нього доступ. Шаблон відокремлює абстракцію від реалізації, щоб їх можна було змінювати без відповідних змін в іншому класі. Наприклад:

protocol Switch {
    var appliance: Appliance {get set}
    func turnOn()}
 
protocol Appliance {
    func run()}
 
class RemoteControl: Switch {
    var appliance: Appliance
 
    func turnOn(){
        self.appliance.run()}
 
    init(appliance: Appliance){
        self.appliance = appliance
    }}
 
class TV: Appliance {
    func run(){
        print("телевізор увімкнено");
    }}
 
class VacuumCleaner: Appliance {
    func run(){
        print("пилосос увімкнено")}}
 
//===main.swift====
 
var vacCleaner = VacuumCleaner()
var control = RemoteControl(appliance: vacCleaner)
 
control.turnOn()

Тепер ви можете змінювати методи run() без внесення коректив у головний файл.

Спостерігач (поведінкова категорія)

Спостерігач передбачає, що об'єкти сповіщають інші про зміну свого стану. Один об'єкт зазвичай «підписується» на зміни іншого. Пуш-повідомлення є глобальним прикладом. Більш локальним прикладом є сповіщення та спостереження за ключовими значеннями (KVO).

Моменто (поведінкова категорія)

Моменто зберігає ваші об'єкти, такі як UserDefaults, Archiving та NSCoding protocol, за допомогою CoreData.

Команда (поведінкова категорія)

Коли ми підключаємо метод до дії при натисканні на будь-який інтерфейс (наприклад, кнопки), це шаблон Команда.

Останні думки

Більше інформації ви можете знайти в наступних статтях:

https://www.raywenderlich.com/46988/ios-design-patterns

https://github.com/ochococo/Design-Patterns-In-Swift

А також у книзі:

Pro Design Patterns in Swift від Адама Фрімана.