Upgrade to Pro — share decks privately, control downloads, hide ads and more …

アプリケーションデザイン_5.pdf

0e742980c48d689f31700b208f22fd5a?s=47 Kohei Kawaguchi
July 19, 2019
96

 アプリケーションデザイン_5.pdf

0e742980c48d689f31700b208f22fd5a?s=128

Kohei Kawaguchi

July 19, 2019
Tweet

Transcript

  1. "QQMJDBUJPO%FTJHO ษڧձ ষ ஜ೾େֶ ৘ใϝσΟΞ૑੒ֶྨ೥ ઒ޱ ߤฏ 1

  2. 4JOHMFUPOύλʔϯͱ .POPTUBUFύλʔϯ 2

  3. ໰୊ • プログラムが開始した際に⽣成され,終了する際に処分すべきインスタンスがある. • オブジェクトの⽣成を⾏うFactoryのような存在. • オブジェクトを管理する存在. • このような存在が複製⽣成されると論理破綻を起こすといった問題が発⽣する. 例)どのインスタンスに対しても同⼀の結果を期待するが,実際は参照するインスタンスによって結果

    が異なってしまう. 3
  4. ໨త • プログラムが開始した際に⽣成され,終了する際に処分すべきインスタンスがある. • オブジェクトの⽣成を⾏うFactoryのような存在. • オブジェクトを管理する存在. • このような存在が複製⽣成されると論理破綻を起こす. 例)どのインスタンスに対しても同⼀の結果を期待するが,実際は参照するインスタンスによって結果

    が異なってしまう. 4 4JOHMFUPO.POPTUBUFύλʔϯΛ༻͍ͯ Πϯεελϯεͷ།ҰੑΛอূ͢Δ
  5. final class SingltonClass { private static var shared: SingltonClass? =

    nil static func instance() -> SingltonClass { if let shared = self.shared { return shared } return .init() } } 4JOHMFUPOύλʔϯ • 特徴 • PublicかつStaticなメソッドを通じてシステム上において唯⼀のインスタンスを取得する. • Publicなコンストラクタを持たない. 5
  6. 4JOHMFUPOύλʔϯΛ༻͍ΔϝϦοτٴͼσϝϦοτ • クロスプラットフォーム • RMIなどを⽤いて,他のJVMに共有できる. • あらゆるクラスに適応可能 • コントラスタをprivateにして共有インスタンスを返すメソッドを⽣ やせば良い

    • 派⽣を使って⽣成することも可能 • ⾃分を返すのではなくて,⼦クラスを返せる. • 評価が簡単 • Singletonが使わなければインスタンスは決して⽣成されない. • オブジェクトの破壊が定義されていない • シングルトンへの参照を持ったあるクラスがそのシングルトンを破 壊すると,別のクラスがそのシングルトンに参照しようとすると別 のシングルトンインスタンスが⽣成されてしまう. • シングルトンを破壊するいい⽅法がない. • 継承できない • 単純に継承しただけでは派⽣クラスはシングルトンになってくれな い. • 効率性 • インスタンスを⽣成するたび(以降,これをinstance()と呼ぶ)にIf ⽂が実⾏されるが,それが役⽴つのは最初の⼀回だけ. • 分かりづらい • Instance()を介することを知っていなければならない. 6 メリット デメリット
  7. .POPTUBUFύλʔϯ • 特徴 • 異なる2つ以上のインスタンスに対してそれらが同⼀であるように振る舞わせることによって,共有するインスタンスの唯⼀性を 保証する⽅法. • Singletonが唯⼀性を保証する構造に着⽬している⼀⽅で,Monostateパターンは振る舞いを保証している. • Monostateパターンは振る舞いを担保しているので,Singletonのテストケースもパス出来る.

    7 final class Monostate { private static var value: Int = 0 func set(value: Int) { Monostate.value = value } func get() -> Int { return Monostate.value } }
  8. .POPTUBUFύλʔϯΛ༻͍ΔϝϦοτٴͼσϝϦοτ • わかりやすさ • クライアントはMonostateを普通のオブジェクトと同じようなイン タフェースから扱える. • 派⽣可能 • MonostateのサブクラスはMonostateである

    • ポリモーフィズム • Monostateのメソッドはstaticではないので,派⽣クラスの中でオー バーライド出来る • ⽣成と破壊の定義が明確 • Monostateの変数はstaticであるから • 変換不可能 • あるMonosteateではないクラスを継承してMonosteateにすると いったことが出来ない. • 効率性 • Monostateは本物のオブジェクトを⽣成する • 存在 • Monostateの変数は全く使われなくともメモリスペースを消費する. • プラットフォームに依存 • JVMのインスタンスあるいは複数のプラットフォームから Monostateを共有することが出来ない. 8 メリット デメリット
  9. .POPTUBUFύλʔϯΛ༻͍ͨྫ • コインを⼊れたらアンロックされ通過出来る⾃動改札機を例とする. • ロックされた状態のときに(コインを⼊れずに)通過しようとするとアラームが鳴る. • アンロックされた状態のときにコインを⼊れると払い戻しされる. • Monostateパターンを⽤いて実装してみる. 9

  10. .POPTUBUFύλʔϯΛ༻͍ͨྫ • 特徴 • 異なる2つ以上のインスタンスに対してそれらが同⼀であるように振る舞わせることによって,共有するインスタンスの唯⼀性を 保証する⽅法. • Singletonが唯⼀性を保証する構造に着⽬している⼀⽅で,Monostateパターンは振る舞いを保証している. • Monostateパターンは振る舞いを担保しているので,Singletonのテストケースもパス出来る.

    10 class Turnstile { static var isLocked = true static var isAlarming = false static var coins = 0 static var refunds = 0 static let locked = Locked() static let unlocked = Unlocked() static var state: Turnstile = locked func reset() { Turnstile.isLocked = true Turnstile.isAlarming = false Turnstile.coins = 0 Turnstile.refunds = 0 Turnstile.state = Turnstile.locked } func deposit() { Turnstile.coins += 1 } func refund() { Turnstile.refunds += 1 } func pass() { Turnstile.state.pass() } func coin() { Turnstile.state.coin() } } final class Locked: Turnstile { override func coin() { Locked.state = Locked.unlocked Locked.isLocked = false Locked.isAlarming = false deposit() } override func pass() { Locked.isAlarming = true } } final class Unlocked: Turnstile { override func coin() { refund() } override func pass() { Unlocked.isLocked = true Unlocked.state = Unlocked.unlocked } }
  11. .POPTUBUFύλʔϯΛ༻͍ͨྫ • 特徴 • 異なる2つ以上のインスタンスに対してそれらが同⼀であるように振る舞わせることによって,共有するインスタンスの唯⼀性を 保証する⽅法. • Singletonが唯⼀性を保証する構造に着⽬している⼀⽅で,Monostateパターンは振る舞いを保証している. • Monostateパターンは振る舞いを担保しているので,Singletonのテストケースもパス出来る.

    11 class Turnstile { static var isLocked = true static var isAlarming = false static var coins = 0 static var refunds = 0 static let locked = Locked() static let unlocked = Unlocked() static var state: Turnstile = locked func reset() { Turnstile.isLocked = true Turnstile.isAlarming = false Turnstile.coins = 0 Turnstile.refunds = 0 Turnstile.state = Turnstile.locked } func deposit() { Turnstile.coins += 1 } func refund() { Turnstile.refunds += 1 } func pass() { Turnstile.state.pass() } func coin() { Turnstile.state.coin() } } final class Locked: Turnstile { override func coin() { Locked.state = Locked.unlocked Locked.isLocked = false Locked.isAlarming = false deposit() } override func pass() { Locked.isAlarming = true } } final class Unlocked: Turnstile { override func coin() { refund() } override func pass() { Unlocked.isLocked = true Unlocked.state = Unlocked.unlocked } } 2つの状態を派⽣クラスに譲渡(ポリモーフィズム可能)
  12. .POPTUBUFύλʔϯΛ༻͍ͨྫ • 特徴 • 異なる2つ以上のインスタンスに対してそれらが同⼀であるように振る舞わせることによって,共有するインスタンスの唯⼀性を 保証する⽅法. • Singletonが唯⼀性を保証する構造に着⽬している⼀⽅で,Monostateパターンは振る舞いを保証している. • Monostateパターンは振る舞いを担保しているので,Singletonのテストケースもパス出来る.

    12 class Turnstile { static var isLocked = true static var isAlarming = false static var coins = 0 static var refunds = 0 static let locked = Locked() static let unlocked = Unlocked() static var state: Turnstile = locked func reset() { Turnstile.isLocked = true Turnstile.isAlarming = false Turnstile.coins = 0 Turnstile.refunds = 0 Turnstile.state = Turnstile.locked } func deposit() { Turnstile.coins += 1 } func refund() { Turnstile.refunds += 1 } func pass() { Turnstile.state.pass() } func coin() { Turnstile.state.coin() } } final class Locked: Turnstile { override func coin() { Locked.state = Locked.unlocked Locked.isLocked = false Locked.isAlarming = false deposit() } override func pass() { Locked.isAlarming = true } } final class Unlocked: Turnstile { override func coin() { refund() } override func pass() { Unlocked.isLocked = true Unlocked.state = Unlocked.unlocked } } 派⽣クラスもMonostate
  13. .POPTUBUFύλʔϯΛ༻͍ͨྫ 13 class Turnstile { static var isLocked = true

    static var isAlarming = false static var coins = 0 static var refunds = 0 static let locked = Locked() static let unlocked = Unlocked() static var state: Turnstile = locked func reset() { Turnstile.isLocked = true Turnstile.isAlarming = false Turnstile.coins = 0 Turnstile.refunds = 0 Turnstile.state = Turnstile.locked } func deposit() { Turnstile.coins += 1 } func refund() { Turnstile.refunds += 1 } func pass() { Turnstile.state.pass() } func coin() { Turnstile.state.coin() } } final class Locked: Turnstile { override func coin() { Locked.state = Locked.unlocked Locked.isLocked = false Locked.isAlarming = false deposit() } override func pass() { Locked.isAlarming = true } } final class Unlocked: Turnstile { override func coin() { refund() } override func pass() { Unlocked.isLocked = true Unlocked.state = Unlocked.unlocked } } Monostateを普通のクラスに戻すのは難しい !
  14. ·ͱΊ ある特定のオブジェクトをアプリケーション上において1つだけしか⽣成したくない場合がある. Singleton/Monostateパターンはそういった場合に使えるテクニック. SingletonパターンではPrivateなコンストラクタと,static変数もしくはstatic関数を使ってインスタンスの ⽣成の制御を⾏う. Singletonパターンは既存のクラスに簡単な修正を加えるだけで⽤いる事ができる利点を持つが,クライア ントはインスタンスの取得⽅法について知っていなければならない. Monostateパターンは,クライアントにとってわかりやすい形で共有するインスタンスを取得出来る. また,ポリモーフィズムを利⽤しても唯⼀性を保証することが出来る. ⼀⽅で,

    Monostateパターンに依存するとその依存を剥ぐことや,既存のクラスにMonostateパターン適 応するのはSingletonパターンに⽐較して難しい. 14
  15. /VMM0CKFDUύλʔϯ 15

  16. ໰୊ • Nullを返し得るプロパティやメソッドに対して,Nullのチェックを⾏った後に参照を⾏う事がよくある. • しかし,このチェックを忘れてしまうこともしばしば. • Nullではなく特定のエラーを投げるようにコードを変更することによって,この問題は解決するが コードが醜くなる可能性がある. 16 let

    e = Employee().get(named: "Bob") if (e != nil && e.isTiemToPay(Date())) { e.pay() }
  17. /VMM 0CKFDUύλʔϯ • Nullチェックの対象となっていたクラス(インスタンス)を抽象化し,実装をインスタンスがNullの場合 とそうでない場合に分ける. • 従来ではNullを返却してた変数もしくは関数をNullを返さずに,Nullを表すインスタンスを返却するよう に変更する. • Nullを表すクラスは何もしないように実装をする.

    17 <<interface>> AnyObject Object NullObject
  18. /VMM 0CKFDUύλʔϯΛ༻͍ͨྫ 18 protocol EmployeeType { func isTiemToPay(_ date: Date)

    -> Bool func pay() } final class Employee: EmployeeType { private final class NullEmployee: EmployeeType { func isTiemToPay(_ date: Date) -> Bool { return false } func pay() { //何もしない return } } static let Null: EmployeeType = NullEmployee() func isTiemToPay(_ date: Date) -> Bool { // 何かしらの制御 let shouldPay = true return shouldPay } func pay() { // 何かしらの制御 } } // 何かしらの制御 let employee = DB.get(named: "Bob") if employee.isTimeToPay(Date()) { faile() } let employee = DB.get(named: "Bob") if employee == Employee.Null { faile() }
  19. /VMM 0CKFDUύλʔϯΛ༻͍ͨྫ 19 protocol EmployeeType { func isTiemToPay(_ date: Date)

    -> Bool func pay() } final class Employee: EmployeeType { private final class NullEmployee: EmployeeType { func isTiemToPay(_ date: Date) -> Bool { return false } func pay() { //何もしない return } } static let Null: EmployeeType = NullEmployee() func isTiemToPay(_ date: Date) -> Bool { // 何かしらの制御 let shouldPay = true return shouldPay } func pay() { // 何かしらの制御 } } // 何かしらの制御 let employee = DB.get(named: "Bob") if employee.isTimeToPay(Date()) { faile() } let employee = DB.get(named: "Bob") if employee == Employee.Null { faile() } 何もしないNullEmployeeを作る
  20. /VMM 0CKFDUύλʔϯΛ༻͍ͨྫ 20 protocol EmployeeType { func isTiemToPay(_ date: Date)

    -> Bool func pay() } final class Employee: EmployeeType { private final class NullEmployee: EmployeeType { func isTiemToPay(_ date: Date) -> Bool { return false } func pay() { //何もしない return } } static let Null: EmployeeType = NullEmployee() func isTiemToPay(_ date: Date) -> Bool { // 何かしらの制御 let shouldPay = true return shouldPay } func pay() { // 何かしらの制御 } } // 何かしらの制御 let employee = DB.get(named: "Bob") if employee.isTimeToPay(Date()) { faile() } let employee = DB.get(named: "Bob") if employee == Employee.Null { faile() } NullEmployeeは必ずfalseを返すので, Nullチェックは不要
  21. /VMM 0CKFDUύλʔϯΛ༻͍ͨྫ 21 protocol EmployeeType { func isTiemToPay(_ date: Date)

    -> Bool func pay() } final class Employee: EmployeeType { private final class NullEmployee: EmployeeType { func isTiemToPay(_ date: Date) -> Bool { return false } func pay() { //何もしない return } } static let Null: EmployeeType = NullEmployee() func isTiemToPay(_ date: Date) -> Bool { // 何かしらの制御 let shouldPay = true return shouldPay } func pay() { // 何かしらの制御 } } // 何かしらの制御 let employee = DB.get(named: "Bob") if employee.isTimeToPay(Date()) { faile() } let employee = DB.get(named: "Bob") if employee == Employee.Null { faile() } NullEmployeeをシングルトンにすることで 従来のような⽐較も可能
  22. ·ͱΊ • Null Objectパターンを⽤いることによって,Nullチェックを⾏う必要が無くなる. • なぜなら,たとえ何かしらの処理が失敗しても「なにもしない」オブジェクトが返却されることが保証 されているから... 22

  23. Ҿ༻ • ロバート・C・マーチンほか.アジャイルソフトウェア開発の奥義 第 2版 オブジェクト指向開発の神髄と匠の技. SBクリエイティブ, 2008 23