![템플릿 메서드](/images/patterns/cards/template-method-mini.png?id=9f200248d88026d8e79d0f3dae411ab4)
스위프트로 작성된 템플릿 메서드
템플릿 메서드는 기초 클래스에서 알고리즘의 골격을 정의할 수 있도록 하는 행동 디자인 패턴입니다. 또 이 패턴은 자식 클래스들이 전체 알고리즘의 구조를 변경하지 않고도 기본 알고리즘의 단계들을 오버라이드할 수 있도록 합니다.
복잡도:
인기도:
사용 사례들: 템플릿 메서드 패턴은 스위프트 프레임워크들에서 매우 일반적이며 개발자들은 종종 이 패턴을 사용하여 프레임워크 사용자들에게 상속을 사용하여 표준 기능들을 확장하는 간단한 수단을 제공합니다.
식별: 템플릿 메서드는 기초 클래스에 추상적이거나 비어 있는 다른 여러 메서드들을 호출하는 메서드가 있습니다.
개념적인 예시
이 예시는 템플릿 메서드의 구조를 보여주고 다음 질문에 중점을 둡니다:
- 패턴은 어떤 클래스들로 구성되어 있나요?
- 이 클래스들은 어떤 역할을 하나요?
- 패턴의 요소들은 어떻게 서로 연관되어 있나요?
이 패턴의 구조를 배우면 실제 스위프트 언어의 사용 사례를 기반으로 하는 다음 예시를 더욱 쉽게 이해할 수 있을 것입니다.
Example.swift: 개념적인 예시
import XCTest
/// The Abstract Protocol and its extension defines a template method that
/// contains a skeleton of some algorithm, composed of calls to (usually)
/// abstract primitive operations.
///
/// Concrete subclasses should implement these operations, but leave the
/// template method itself intact.
protocol AbstractProtocol {
/// The template method defines the skeleton of an algorithm.
func templateMethod()
/// These operations already have implementations.
func baseOperation1()
func baseOperation2()
func baseOperation3()
/// These operations have to be implemented in subclasses.
func requiredOperations1()
func requiredOperation2()
/// These are "hooks." Subclasses may override them, but it's not mandatory
/// since the hooks already have default (but empty) implementation. Hooks
/// provide additional extension points in some crucial places of the
/// algorithm.
func hook1()
func hook2()
}
extension AbstractProtocol {
func templateMethod() {
baseOperation1()
requiredOperations1()
baseOperation2()
hook1()
requiredOperation2()
baseOperation3()
hook2()
}
/// These operations already have implementations.
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() {}
}
/// Concrete classes have to implement all abstract operations of the base
/// class. They can also override some operations with a default implementation.
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")
}
}
/// Usually, concrete classes override only a fraction of base class'
/// operations.
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")
}
}
/// The client code calls the template method to execute the algorithm. Client
/// code does not have to know the concrete class of an object it works with, as
/// long as it works with objects through the interface of their base class.
class Client {
// ...
static func clientCode(use object: AbstractProtocol) {
// ...
object.templateMethod()
// ...
}
// ...
}
/// Let's see how it all works together.
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