
Patron de méthode en Swift
Le Patron de méthode est un patron de conception comportemental qui permet de définir le squelette d’un algorithme dans la classe de base, et laisse les sous-classes redéfinir les étapes sans modifier la structure globale de l’algorithme.
Utilisation du patron de conception en Swift
Complexité :
Popularité :
Exemples d’utilisation : Le patron de méthode est assez répandu en Swift. Les développeurs l’emploient souvent pour fournir un framework qui permet aux utilisateurs d’étendre des fonctionnalités standards avec l’héritage.
Identification : Le patron de méthode peut être reconnu par ses méthodes comportementales qui ont déjà un comportement « par défaut » défini dans la classe de base.
Exemple conceptuel
Dans cet exemple, nous allons voir la structure du Patron de méthode et répondre aux questions suivantes :
- Que contiennent les classes ?
- Quels rôles jouent-elles ?
- Comment les éléments du patron sont-ils reliés ?
Après avoir étudié la structure du patron, vous pourrez plus facilement comprendre l’exemple suivant qui est basé sur un cas d’utilisation réel en Swift.
Example.swift: Exemple conceptuel
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: Résultat de l’exécution
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
Analogie du monde réel
Example.swift: Analogie du monde réel
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: Résultat de l’exécution
You have access to Camera
You have access to Microphone
PhotoLibrary Accessor: Rejected with access. Updating analytics...
You do not have access to PhotoLibrary