Онлайн курсы по Swift

[vc_row][vc_column][vc_column_text]Начните учиться прямо сейчас! Смотрите, как использовать их, чтобы создавать чистый и удобный в поддержке код, а также экономить время, когда нужно быстро изменить определенный функционал.

Design Pattern — это частая тема для разговоров, форумов и даже для 15-минутного перерыва на работе. Вы можете найти много вещей в книгах или интернете об этом, а также множество примеров использования.

Когда я начинал учиться, я понимал, что такое паттерны, но я не понимал, как их применить в моем коде. Я понимал, что Factory Method используется для создания объектов, но не понимал, зачем он мне нужен. Моя цель в этом посте — привести некоторые реальные примеры шаблонов дизайна, которые я использовал в своих проектах.

 

Strategy pattern (Поведенческие шаблоны)

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

Мы можем использовать различные алгоритмы во время выполнения программы: другими словами, нам все равно, как выполнена реализация.

Алгоритму нужен объектfly, который знает, как летаетclass утка, а также знает, как летает class ракета. И ему совсем не важно, что используется: взмах крыла или газовый баллон.

 

protocol Fly {
  protocol Fly {
  func fly()
}

class Duck: Fly {
  func fly() {
    print("spread wings")
  }
}

class Rocket: Fly {
  func fly() {
    print("vrooommm!!")
  }
}

let flyableObject: Fly = Rocket()
flyableObject.fly()

 

Решение задачи с помощью стратегии

Для этого проекта нам понадобится опыт работы с viewController, а также заранее определенная модель поведения (Strategy pattern). Идеальный пример для использования Strategy Pattern — это реализация с помощью viewController процесса авторизации (Login). Создадим протокол LoginViewController и определим его действия и зависимости.

Когда мы внедряем LoginViewController в наш проект, нам не надо думать о пользовательском интерфейсе, сделан ли он с помощью tableView, использует ли анимацию или методы проверки. Главное, чтобы он знал, как выполнить авторизацию.

 

protocol LoginViewControllerActions {
    func loginBtnPressed(user: User)
}

//swift 3
protocol LoginViewController: UIViewController {
    let user: User
    var delegate: LoginViewControllerActions?
}

 

Factory (Фабричный) метод

Factory Method — это паттерн, который используется для создания объектов без указания его точного класса.

Он полезен тогда, когда нужно создать объект во время выполнения программы. Например, если пользователь хочет пиццу с сыром, то будет создан объект CheesePizza(), а если он хочет пиццу с пепперони, то PepperoniPizza().

 

enum PizzaType {
  case cheese
  case pepperoni
  case greek
}

class PizzaFactory {

  func build(type: PizzaType) -> Pizza {
    switch type {
    case cheese:
      return CheesePizza()
    case pepperoni:
      return PepperoniPizza()
    case greek:
      return GreekPizza()
    }
  }
  
}

 

Решение проблем с помощью Factory

Ранее мы использовали поведенческий (Strategy) шаблон для авторизации с помощью viewController. Чтобы получить возможность создавать объекты во время выполнения программы и передавать все зависимости, в которых они нуждаются, мы будем использовать factory pattern.

Мы должны запушить экземпляр класса LoginViewController, который можно создать, используя разные подходы, например, с помощью storyboard, xibx или программным путем. У нас есть Factory пример, который может быть инжектирован таким образом, что объекты, которые создаются c помощью него, будут менять сам Factory.

 

protocol LoginViewControllerActions {
    func loginBtnPressed(user: User)
}

//swift 3
protocol LoginViewController: UIViewController {
    let user: User
    var delegate: LoginViewControllerActions?
}

protocol LoginViewControllerFactory {
    func build(delegate: LoginViewControllerActions) -> LoginViewController
}

class ViewCodedLoginViewControllerFactory: LoginViewControllerFactory {
  func build(delegate: LoginViewControllerActions) -> LoginViewController {
    return ViewCodedLoginViewController(delegate: delegate) 
  }
}

class StoryboardLoginViewControllerFactory: LoginViewControllerFactory {
  func build(delegate: LoginViewControllerActions) -> LoginViewController {
     let viewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("LoginViewController") as LoginViewController
     viewController.delegate = delegate
     return viewController
  }
}

 

С классом Factory нам просто нужно вызвать метод build. Нас не интересует, какой Factory метод будет запрошен: ViewCodedLoginViewControllerFactory или StoryboardLoginViewControllerFactory — нам просто нужно выполнить метод build, который вернет нам LoginViewController.

 

let viewController = factory.build(delegate: self) //LoginViewControllerFactory
self.presentViewController(viewController, animated: false, completion: nil)

 

У нас получился один Factory метод, который знает, как собрать ViewController с помощью storyboard, xibx или программным путем.

 

Декоратор

Декоратор (Decorator) — это структурный шаблон, который позволяет динамически добавлять объектам дополнительное поведение, не влияя на поведение других объектов из этого же класса.

Это мой любимый шаблон. Классический пример — работа кофейни, где при добавлении нового ингредиента к кофе, например, взбитых сливок, необходимо рассчитать новую стоимость и получить описание состава напитка.

 

protocol Beverage {
    func cost() -> Double
    func description() -> String
}

class Coffee: Beverage {
    func cost() -> Double {
        return 0.95
    }
    
    func description() -> String {
        return "Coffe"
    }
}

class Whip: Beverage {
    let beverage: Beverage
    
    init(beverage: Beverage) {
        self.beverage = beverage
    }
    
    func cost() -> Double {
        return 0.45 + self.beverage.cost()
    }
    
    func description() -> String {
        return self.beverage.description() + ", Whip"
    }
}

var darkRoast: Beverage = Coffee()
darkRoast = Whip(beverage: darkRoast)

darkRoast.description()
darkRoast.cost()

 

Решение проблемы с помощью декоратора

Нам нужны различные версии API для каждого служебного вызова, это можно реализовать различными способами, но мы воспользуемся Decorator pattern для добавления пользовательского заголовка Header в шаблон Request. При таком подходе мы будем готовы, если в будущем для API вызова нужно будет добавить новый параметр в заголовок.

 

public typealias JsonObject = [String : Any]

public protocol Request {
    func request(method: HTTPMethod, data: JsonObject, header: JsonObject?, completion: @escaping (Result) -> Void)
}

public class MyRequest: Request {
    
    public init() {
        
    }
    
    public func request(method: HTTPMethod, data: JsonObject, header: JsonObject?, completion: @escaping (Result) -> Void) {
        //do request
    }
}

public class MyHeader: Request {
    
    let request: Request
    let header: [String: String]
    
    public init(request: Request, apiVersion: APIVersion = .standard){
        self.request = request
        self.header = ["myapikey": "apiKey",
                       "key" : "key",
                       "version" :  "\(apiVersion.rawValue)"]

    }
    
    public func request(method: HTTPMethod, data: JsonObject, header: JsonObject?, completion: @escaping (Result) -> Void) {
        
        let mutableHeader = self.header + (header ?? [:])
        
        self.request.request(method: method, data: data, header: mutableHeader, completion: completion)
    }
}

let v1Request: Request = MyHeader(request: MyRequest(), apiVersion: .v1)
let standardRequest: Request = MyHeader(request: MyRequest())

 

Нам также нужно отфильтровать результаты в запросе. Это можно сделать, создав новый метод или изменив способ работы самого запроса. Мы решили использовать Декоратор, чтобы добавить подобное поведение, так как наш сервис используется в других классах, желательно менять как можно меньше строк кода в предыдущей имплементации.

 

protocol Service {
  func fetch(completion: @escaping (Result<[String]>) -> Void) -> Void 
}

class ViewControllerLoader<D> {
    func load(completion: @escaping (Result<D>) -> Void) {
      fatalError("load method need to be override on subclasses")
    }
}

class ServiceViewControllerLoader: ViewControllerLoader<[String]> {
  
  let service: Service
  
  init(service: Service) {
        self.service = service
  }
    
    override func load(completion: @escaping (Result<[String]>) -> Void) {
        self.service.fetch() { (result) in
            switch result {
            case .success(let strings):
                completion(.success(strings))
            case .error(let error):
                completion(.error(error))
                
            }
        }
    }
}

class ServiceViewControllerLoaderDecorator: ViewControllerLoader<[String]> {
  
  let loader: ViewControllerLoader<[String]>
  
  init(loader: ViewControllerLoader<[String]>) {
        self.loader = loader
  }
  
  func filter(data: [String]) {
    //do filtering 
  }
  
  override func load(completion: @escaping (Result<[String]>) -> Void) {
        
        self.loader.service.fetch { (result) in
            switch result {
            case .success(let strings):
                let filteredStrings = self.filter(data: strings)
                completion(.success(filteredStrings))
            case .error(let error):
                completion(.error(error))
            }
        }
    }
}

 

Адаптер

Адаптер (Adapter) — это структурный шаблон проектирования, который обеспечивает совместную работу классов с несовместимыми интерфейсами. Он часто используется, чтобы заставить работать существующие классы с другими без изменения их исходного кода.

Принцип работы шаблона соответствует своему названию —адаптер. Представьте, что вам нужно, чтобы ваш Nintendo 64, который использует композитный видеовыход, мог работать с вашим новым 4K-телевизором. Для этого вам понадобится Composite-HDMI адаптер.

 

Решение проблем с помощью адаптера

Нам нужно получить последние четыре цифры модели card, а также нужна поддержка PKPPaymentPass. Другими словами, нам нужно создать адаптер, который будет поддерживать интерфейс PKPPaymentPass совместно с интерфейсом card.

 

public struct Card {
    var lastNumber: String = ""
}

public struct PassKitCard {
    
    let passKitCard: PKPaymentPass?
    
    public func toCard() -> Card {
        return Card(lastNumber: paymentPass.primaryAccountNumberSuffix)
    }
}

Но мы также можем смешать шаблоны, чтобы сделать наш код многоразовым и поддерживаемым, например, добавив поведенческий шаблон Strategy. На самом деле нам не нужен объект card, нам нужны только его последние четыре цифры (lastNumbers). Так почему бы нам не создать интерфейс, который будет совместим с PKPaymentPass, Card и любым другим объектом, который может понадобиться нашему проекту.

 

public protocol LastNumber {
    var lastNumber: String { get }
}

public struct PassKitLastNumber: LastNumber {
    
    let passKitCard: PKPaymentPass?
    
    public var lastNumber: String {
        
        if let paymentPass = self.passKitCard {
            return paymentPass.primaryAccountNumberSuffix
        }
        
        return ""
    }
}

class Card: LastNumber {
    
    let card: Card
    
    init(card: Card) {
        self.card = card
    }
    
    var lastNumber: String {
        return self.card.lastNumbers
    }
}

 

Заключение

Шаблоны программирования помогают создать чистый и удобный в поддержке код, а также сэкономить время, когда нужно быстро изменить определенный функционал. Поэтому решение некоторых задач становится значительно проще.[/vc_column_text][/vc_column][/vc_row][vc_row][vc_column][vc_zigzag][/vc_column][/vc_row][vc_row css=».vc_custom_1547303958943{padding-top: 50px !important;}»][vc_column][vc_column_text]

Курсы iOS

На наших онлайн курсах вы освоите азы iOS-разработки и начнёте писать чистый код, соответствующий современным стандартам.

 

[dt_default_button link=»https://swiftlab.ru/courses/» button_alignment=»default» animation=»fadeIn» size=»big» default_btn_bg_color=»» bg_hover_color=»» text_color=»» text_hover_color=»» icon=»fa fa-chevron-circle-right» icon_align=»left»]ПЕРЕЙТИ К КУРСАМ[/dt_default_button]

[/vc_column_text][/vc_column][/vc_row]

Пролистать наверх