はじめに
その2の続き。今回はオブジェクトの振る舞いに関するパターンについてです。
- オブジェクトの振る舞いに関するパターン
- Iterator パターン
- Command パターン
- Chain of Responsibility パターン
- Memento パターン
- Observer パターン
- Mediator パターン
- Interpreter パターン
- State パターン
- Strategy パターン
- Template Method パターン
- Visitor パターン
その1、2同様、基本的には下記2つを参考に書いてます。
github でこんなんみつけたのでこれ見ればいいと思うよ!(私まだ理解してないので。。。)
ochococo/Design-Patterns-In-Swift
Iterator パターン
オブジェクト集合の構造に依存することなく走査、順次アクセスを可能にするための仕組み。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
struct Dog { let name: String } struct Dogs { let dogs: [Dog] } struct DogsIterator: IteratorProtocol { private var current = 0 private let dogs: [Dog] init(dogs: [Dog]) { self.dogs = dogs } mutating func next() -> Dog? { defer { current += 1 } return dogs.count > current ? dogs[current] : nil } } extension Dogs: Sequence { func makeIterator() -> DogsIterator { return DogsIterator(dogs: dogs) } } class Hoge { func hogehoge() { let dogs = Dogs(dogs: [.init(name: "いぬ"), .init(name: "いっぬ")]) for dog in dogs { print(dog.name) } } } |
配列じゃなくても配列みたいに繰り返し処理ができる!
Command パターン
一連の処理群を1つのオブジェクトにまとめて扱う仕組み。マクロみたいな感じで使える。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
protocol DogOperationCommand { init(name: String) func execute() } class DogRunCommand: DogOperationCommand { let name: String required init(name: String) { self.name = name } func execute() { print("\(name) Run!") } } class DogJumpCommand: DogOperationCommand { let name: String required init(name: String) { self.name = name } func execute() { print("\(name) Jump!") } } class DogManager { let runCommand: DogOperationCommand let jumpCommand: DogOperationCommand init(runCommand: DogRunCommand, jumpCommand: DogJumpCommand) { self.runCommand = runCommand self.jumpCommand = jumpCommand } func run() { runCommand.execute() } func jump() { jumpCommand.execute() } } |
Dog の操作はすべて DogManager で行える!この例だと enum でいいんじゃね?ってなるけど処理の中身が複雑になれば力を発揮するはず??
Chain of Responsibility パターン
処理に関わるオブジェクトを鎖状につないで処理できるオブジェクトが見つかるまでたどる仕組み。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
protocol Approving { func approve(level: Int) -> Bool } class Approver: Approving { let level: Int var next: Approving? init(level: Int, next: Approving?) { self.level = level self.next = next } func approve(level: Int) -> Bool { if self.level >= level { return true } if let next = self.next { return next.approve(level: level) } return false } } class PiyoRequest: Approving { private var paisen: Approving private var shunin: Approving private var kakaricho: Approving private var kacho: Approving private var startApprover: Approving { return self.paisen } init(paisen: Approving, shunin: Approving, kakaricho: Approving, kacho: Approving) { self.paisen = paisen self.shunin = shunin self.kakaricho = kakaricho self.kacho = kacho } func approve(level: Int) -> Bool { return startApprover.approve(level: level) } } class Hoge { func hogehoge() { let kacho = Approver(level: 10, next: nil) let kakaricho = Approver(level: 7, next: kacho) let shunin = Approver(level: 3, next: kakaricho) let paisen = Approver(level: 0, next: shunin) let piyoRequest = PiyoRequest.init(paisen: paisen, shunin: shunin, kakaricho: kakaricho, kacho: kacho) if piyoRequest.approve(level: 3) { print("Approve!!") } else { print("Reject!!") } if piyoRequest.approve(level: 100) { print("Approve!!") } else { print("Reject!!") } } } |
レベル3の要求は主任が承認してくれるけどレベル100の要求はだれも権限を持ってないので承認できない!!!
参考:マンガでわかる Chain of Responsibility
Memento パターン
オブジェクトの状態を記憶して、過去の状態に復帰できるようにする仕組み。つまり cmd + Z 機能!(Memento の単語の意味を調べると「記念品」、「形見」とかでてきた)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
struct Memento { var value: String } class Originator { private var value: String = "" func inputText(_ text: String) { value = text } func printText() { print(value) } func saveMemento() -> Memento { return Memento(value: value) } func restoreMemento(_ memento: Memento) { value = memento.value } } class Caretaker { var histories = [Memento]() func addMemento(_ memento: Memento) { histories.append(memento) } func popMemento() -> Memento? { return histories.popLast() } } class Hoge { func hogehoge() { let caretaker = Caretaker() let originator = Originator() originator.inputText("input1") caretaker.addMemento(originator.saveMemento()) originator.inputText("input2") originator.printText() // input2 originator.restoreMemento(caretaker.popMemento()!) originator.printText() // input1 } } |
Observer パターン
状態の変化を通知して監視対象に通知する仕組み。(Listenerともいうらしい。Android の onClickLisner とかがそうなのかも??)
通知を発行するオブジェクトを Subject, 通知を待つオブジェクト(観測者)を Observer という。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
protocol Observer: NSObject { func didChange(subject: Subject) } protocol Subject: AnyObject { var observers: [Observer] { get } var status: Int { get } func addObserver(_ observer: Observer) func removeObserver(_ observer: Observer) } class Piyo: Subject { var observers: [Observer] = [] var status: Int = 0 { didSet { for observer in observers { observer.didChange(subject: self) } } } func addObserver(_ observer: Observer) { if !observers.contains(where: { $0 == observer }) { observers.append(observer) } } func removeObserver(_ observer: Observer) { if let index = observers.firstIndex(where: { $0 == observer }) { observers.remove(at: index) } } } class Hoge: NSObject, Observer { func hogehoge() { let subject = Piyo() subject.addObserver(self) subject.status = 10 } func didChange(subject: Subject) { print("Change subject! status: \(subject.status)") } } |
KVO とか NotificationCenter が Observer パターンだと思う。
Mediator パターン
オブジェクト間の相互作用を仲介してオブジェクト間の結合度を低くする仕組み。(Mediator は仲介者という意味っぽい)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
protocol Mediator: AnyObject { associatedtype ColleagueType: Colleague var colleagues: [ColleagueType] { get } func addColleague(_ colleague: ColleagueType) func send(colleague: ColleagueType, message: String) } protocol Colleague: NSObject { associatedtype MediatorType: Mediator var mediator: MediatorType { get } func send(message: String) func receive(message: String) } class Leader: Mediator { var colleagues: [Programer] = [] func addColleague(_ colleague: Programer) { colleagues.append(colleague) } func send(colleague: Programer, message: String) { for programer in colleagues { if programer == colleague { continue } programer.receive(message: message) } } } class Programer: NSObject, Colleague { let mediator: Leader init(mediator: Leader) { self.mediator = mediator } func send(message: String) { mediator.send(colleague: self, message: message) } func receive(message: String) { print(message) } } class Hoge { func hogehoge() { let leader = Leader() let programerA = Programer(mediator: leader) leader.addColleague(programerA) let programerB = Programer(mediator: leader) leader.addColleague(programerB) programerA.send(message: "プログラマーAですよ") } } |
プログラマーAはプログラマーBのことを知らなくてもリーダーを介してメッセージを送れるようになった!
Interpreter パターン
計算式や演算の構文木を評価して解を求める仕組みらしいです。。。
ちょっとこれは全然理解できなかったので↓のやつも ochococo/Design-Patterns-In-Swift から拝借しました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
protocol IntegerExpression { func evaluate(_ context: IntegerContext) -> Int func replace(character: Character, integerExpression: IntegerExpression) -> IntegerExpression func copied() -> IntegerExpression } final class IntegerContext { private var data: [Character:Int] = [:] func lookup(name: Character) -> Int { return self.data[name]! } func assign(expression: IntegerVariableExpression, value: Int) { self.data[expression.name] = value } } final class IntegerVariableExpression: IntegerExpression { let name: Character init(name: Character) { self.name = name } func evaluate(_ context: IntegerContext) -> Int { return context.lookup(name: self.name) } func replace(character name: Character, integerExpression: IntegerExpression) -> IntegerExpression { if name == self.name { return integerExpression.copied() } else { return IntegerVariableExpression(name: self.name) } } func copied() -> IntegerExpression { return IntegerVariableExpression(name: self.name) } } final class AddExpression: IntegerExpression { private var operand1: IntegerExpression private var operand2: IntegerExpression init(op1: IntegerExpression, op2: IntegerExpression) { self.operand1 = op1 self.operand2 = op2 } func evaluate(_ context: IntegerContext) -> Int { return self.operand1.evaluate(context) + self.operand2.evaluate(context) } func replace(character: Character, integerExpression: IntegerExpression) -> IntegerExpression { return AddExpression(op1: operand1.replace(character: character, integerExpression: integerExpression), op2: operand2.replace(character: character, integerExpression: integerExpression)) } func copied() -> IntegerExpression { return AddExpression(op1: self.operand1, op2: self.operand2) } } class Hoge() { func hogehoge() { var context = IntegerContext() var a = IntegerVariableExpression(name: "A") var b = IntegerVariableExpression(name: "B") var c = IntegerVariableExpression(name: "C") var expression = AddExpression(op1: a, op2: AddExpression(op1: b, op2: c)) // a + (b + c) context.assign(expression: a, value: 2) context.assign(expression: b, value: 1) context.assign(expression: c, value: 3) var result = expression.evaluate(context) } } |
State パターン
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
class Context { private var state: State = Hungry() var hasMotivation: Bool { return state.hasMotivation(context: self) } func changeStateToHungry() { state = Hungry() } func changeStateToSleepy() { state = Sleepy() } } protocol State { func hasMotivation(context: Context) -> Bool } class Hungry: State { func hasMotivation(context: Context) -> Bool { return false } } class Sleepy: State { func hasMotivation(context: Context) -> Bool { return false } } class Hoge { func hogehoge() { let context = Context() print(context.hasMotivation) // false context.changeStateToHungry() print(context.hasMotivation) // false context.changeStateToSleepy() print(context.hasMotivation) // false } } |
お腹が減ってる状態でも眠い状態でもやる気はない!
今の状態は何か、その状態における振る舞いはこうだ、と一意に決めようという発想らしいです。(振る舞いは外部に分離してしまうってことだと思う。)
Strategy パターン
取り扱う対象に応じて処理オブジェクトを変更して、振る舞いを変える仕組み。Strategy は戦略(アルゴリズム)って意味。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
protocol PrintStrategy { func printString(string: String) -> String } class Printer { let strategy: PrintStrategy func printString(string:String)->String{ return self.strategy.printString(string) } init(strategy: PrintStrategy){ self.strategy = strategy } } class UpperCaseStrategy: PrintStrategy{ func printString(string:String)->String{ return string.uppercaseString } } class LowerCaseStrategy: PrintStrategy{ func printString(string:String)->String{ return string.lowercaseString } } var lower = Printer(strategy: LowerCaseStrategy()) lower.printString("O tempora, o mores!") var upper = Printer(strategy: UpperCaseStrategy()) upper.printString("O tempora, o mores!") |
↑のはいいのが思いつかなかったのでSwiftで書くデザインパターンまとめのやつを拝借しました。
アルゴリズムを分離して分岐がなくなるのがメリット??(オブジェクトを差し替えれば動作が変わるので分岐がなくなる)
Template Method パターン
処理の途中で抽象メソッドを使ってテンプレート処理を実現する仕組み。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
protocol ResDirectoryFileReader { func readFile(fileName: String) func displayFile(filePath: String) } extension ResDirectoryFileReader { func readFile(fileName: String) { let res = NSString(string: fileName).deletingPathExtension let type = NSString(string: fileName).pathExtension if let path = Bundle.main.path(forResource: res, ofType: type) { print(path) displayFile(filePath: path) } } } class ImageReader: UIView, ResDirectoryFileReader { @IBOutlet weak var imageView: UIImageView? func displayFile(filePath: String) { imageView?.image = UIImage(contentsOfFile: filePath) } } class MovieReader: UIView, ResDirectoryFileReader { @IBOutlet weak var webView: WKWebView? func displayFile(filePath: String) { webView?.load(.init(url: .init(fileURLWithPath: filePath))) } } class Hoge: UIViewController { @IBOutlet weak var imageReader: ImageReader? @IBOutlet weak var movieReader: MovieReader? func hogehoge() { imageReader?.readFile(fileName: "sample.png") movieReader?.readFile(fileName: "sample.mov") } } |
あってるかわからないけどこれで動画と画像を表示できるようになった。
Visitor パターン
データの構造と処理を分離して処理の変更に柔軟性を持たせる仕組み。
これもいまいちよくわからなかったので ochococo/Design-Patterns-In-Swift から拝借しました。(なんでそもそも Visitor なんだ??)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
protocol PlanetVisitor { func visit(planet: PlanetAlderaan) func visit(planet: PlanetCoruscant) func visit(planet: PlanetTatooine) func visit(planet: MoonJedha) } protocol Planet { func accept(visitor: PlanetVisitor) } final class MoonJedha: Planet { func accept(visitor: PlanetVisitor) { visitor.visit(planet: self) } } final class PlanetAlderaan: Planet { func accept(visitor: PlanetVisitor) { visitor.visit(planet: self) } } final class PlanetCoruscant: Planet { func accept(visitor: PlanetVisitor) { visitor.visit(planet: self) } } final class PlanetTatooine: Planet { func accept(visitor: PlanetVisitor) { visitor.visit(planet: self) } } final class NameVisitor: PlanetVisitor { var name = "" func visit(planet: PlanetAlderaan) { name = "Alderaan" } func visit(planet: PlanetCoruscant) { name = "Coruscant" } func visit(planet: PlanetTatooine) { name = "Tatooine" } func visit(planet: MoonJedha) { name = "Jedha" } } class Hoge: UIViewController { func hogehoge() { let planets: [Planet] = [PlanetAlderaan(), PlanetCoruscant(), PlanetTatooine(), MoonJedha()] let names = planets.map { (planet: Planet) -> String in let visitor = NameVisitor() planet.accept(visitor: visitor) return visitor.name } print(names) } } |
さいごに
参考サイトにも下記のように書いてあったがなんかええ感じになるんやろ?使って書いた方がクールやん?みたいなパターンを導入するのが目的になると危険なので気をつけよう!"目的のないパターンはただの難読化" ということを肝に銘じよう!
目的のないパターンはただの難読化です。
まとめてみたけど結構意味が理解できてないパターンも多いのでまた後日追記するかも。数年後の自分に期待。。。
参考
- オブジェクト指向言語を学ぶ人のための ザックリ理解するデザインパターン
- マンガでわかる Iterator
- マンガでわかる Command
- マンガでわかる Chain of Responsibility
- マンガでわかる Memento
- マンガでわかる Observer
- マンガでわかる State
- マンガでわかる Strategy
- GoF デザインパターン チートシート
コメント