
Шаблонный метод на Swift
Шаблонный метод — это поведенческий паттерн, задающий скелет алгоритма в суперклассе и заставляющий подклассы реализовать конкретные шаги этого алгоритма.
Сложность:
Популярность:
Применимость: Шаблонные методы можно встретить во многих библиотечных классах Swift. Разработчики создают их, чтобы позволить клиентам легко и быстро расширять стандартный код при помощи наследования.
Признаки применения паттерна: Класс заставляет своих потомков реализовать методы-шаги, но самостоятельно реализует структуру алгоритма.
Концептуальный пример
Этот пример показывает структуру паттерна Шаблонный метод, а именно — из каких классов он состоит, какие роли эти классы выполняют и как они взаимодействуют друг с другом.
После ознакомления со структурой, вам будет легче воспринимать второй пример, который рассматривает реальный случай использования паттерна в мире Swift.
Example.swift: Пример структуры паттерна
import XCTest /// Абстрактный Протокол и его расширение определяет шаблонный метод, содержащий /// скелет некоторого алгоритма, состоящего из вызовов (обычно) абстрактных /// примитивных операций. /// /// Конкретные подклассы должны реализовать эти операции, но оставить сам /// шаблонный метод без изменений. protocol AbstractProtocol { /// Шаблонный метод определяет скелет алгоритма. func templateMethod() /// Эти операции уже имеют реализации. func baseOperation1() func baseOperation2() func baseOperation3() /// А эти операции должны быть реализованы в подклассах. func requiredOperations1() func requiredOperation2() /// Это «хуки». Подклассы могут переопределять их, но это не обязательно, /// поскольку у хуков уже есть стандартная (но пустая) реализация. Хуки /// предоставляют дополнительные точки расширения в некоторых критических /// местах алгоритма. func hook1() func hook2() } extension AbstractProtocol { func templateMethod() { baseOperation1() requiredOperations1() baseOperation2() hook1() requiredOperation2() baseOperation3() hook2() } /// Эти операции уже имеют реализации. func baseOperation1() { print("AbstractProtocol says: I am doing the bulk of the work\n") } func baseOperation2() { print("AbstractProtocol says: But I let subclasses override some operations\n") } func baseOperation3() { print("AbstractProtocol says: But I am doing the bulk of the work anyway\n") } func hook1() {} func hook2() {} } /// Конкретные классы должны реализовать все абстрактные операции базового /// класса. Они также могут переопределить некоторые операции с реализацией по /// умолчанию. class ConcreteClass1: AbstractProtocol { func requiredOperations1() { print("ConcreteClass1 says: Implemented Operation1\n") } func requiredOperation2() { print("ConcreteClass1 says: Implemented Operation2\n") } func hook2() { print("ConcreteClass1 says: Overridden Hook2\n") } } /// Обычно конкретные классы переопределяют только часть операций базового /// класса. class ConcreteClass2: AbstractProtocol { func requiredOperations1() { print("ConcreteClass2 says: Implemented Operation1\n") } func requiredOperation2() { print("ConcreteClass2 says: Implemented Operation2\n") } func hook1() { print("ConcreteClass2 says: Overridden Hook1\n") } } /// Клиентский код вызывает шаблонный метод для выполнения алгоритма. Клиентский /// код не должен знать конкретный класс объекта, с которым работает, при /// условии, что он работает с объектами через интерфейс их базового класса. class Client { // ... static func clientCode(use object: AbstractProtocol) { // ... object.templateMethod() // ... } // ... } /// Давайте посмотрим как всё это будет работать. class TemplateMethodConceptual: XCTestCase { func test() { print("Same client code can work with different subclasses:\n") Client.clientCode(use: ConcreteClass1()) print("\nSame client code can work with different subclasses:\n") Client.clientCode(use: ConcreteClass2()) } }
Output.txt: Результат выполнения
Same client code can work with different subclasses: AbstractProtocol says: I am doing the bulk of the work ConcreteClass1 says: Implemented Operation1 AbstractProtocol says: But I let subclasses override some operations ConcreteClass1 says: Implemented Operation2 AbstractProtocol says: But I am doing the bulk of the work anyway ConcreteClass1 says: Overridden Hook2 Same client code can work with different subclasses: AbstractProtocol says: I am doing the bulk of the work ConcreteClass2 says: Implemented Operation1 AbstractProtocol says: But I let subclasses override some operations ConcreteClass2 says: Overridden Hook1 ConcreteClass2 says: Implemented Operation2 AbstractProtocol says: But I am doing the bulk of the work anyway
Пример из реальной жизни
Example.swift: Пример из реальной жизни
import XCTest import AVFoundation import CoreLocation import Photos class TemplateMethodRealWorld: XCTestCase { /// A good example of Template Method is a life cycle of UIViewController func testTemplateMethodReal() { let accessors = [CameraAccessor(), MicrophoneAccessor(), PhotoLibraryAccessor()] accessors.forEach { item in item.requestAccessIfNeeded({ status in let message = status ? "You have access to " : "You do not have access to " print(message + item.description + "\n") }) } } } class PermissionAccessor: CustomStringConvertible { typealias Completion = (Bool) -> () func requestAccessIfNeeded(_ completion: @escaping Completion) { guard !hasAccess() else { completion(true); return } willReceiveAccess() requestAccess { status in status ? self.didReceiveAccess() : self.didRejectAccess() completion(status) } } func requestAccess(_ completion: @escaping Completion) { fatalError("Should be overridden") } func hasAccess() -> Bool { fatalError("Should be overridden") } var description: String { return "PermissionAccessor" } /// Hooks func willReceiveAccess() {} func didReceiveAccess() {} func didRejectAccess() {} } class CameraAccessor: PermissionAccessor { override func requestAccess(_ completion: @escaping Completion) { AVCaptureDevice.requestAccess(for: .video) { status in return completion(status) } } override func hasAccess() -> Bool { return AVCaptureDevice.authorizationStatus(for: .video) == .authorized } override var description: String { return "Camera" } } class MicrophoneAccessor: PermissionAccessor { override func requestAccess(_ completion: @escaping Completion) { AVAudioSession.sharedInstance().requestRecordPermission { status in completion(status) } } override func hasAccess() -> Bool { return AVAudioSession.sharedInstance().recordPermission == .granted } override var description: String { return "Microphone" } } class PhotoLibraryAccessor: PermissionAccessor { override func requestAccess(_ completion: @escaping Completion) { PHPhotoLibrary.requestAuthorization { status in completion(status == .authorized) } } override func hasAccess() -> Bool { return PHPhotoLibrary.authorizationStatus() == .authorized } override var description: String { return "PhotoLibrary" } override func didReceiveAccess() { /// We want to track how many people give access to the PhotoLibrary. print("PhotoLibrary Accessor: Receive access. Updating analytics...") } override func didRejectAccess() { /// ... and also we want to track how many people rejected access. print("PhotoLibrary Accessor: Rejected with access. Updating analytics...") } }
Output.txt: Результат выполнения
You have access to Camera You have access to Microphone PhotoLibrary Accessor: Rejected with access. Updating analytics... You do not have access to PhotoLibrary