[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]