LoginSignup
2
5

More than 1 year has passed since last update.

Swiftで覚えるデザインパターンその3

Posted at

はじめに

Swiftで覚えるデザインパターン第3弾です。

  1. Swiftで覚えるデザインパターン
    1. Strategy
    2. Observer
    3. Decorator
    4. Factory Method
    5. Abstract Factory
    6. Singleton
  2. Swiftで覚えるデザインパターンその2
    1. Command
    2. Adaptor
    3. Facade
    4. Template Method
  3. Swiftで覚えるデザインパターンその3 ← 今ココ
    1. Iterator
    2. Composite
    3. State
    4. Compound

Iteratorパターン

概要

集約したいくつかのオブジェクト(配列やコレクション) = アグリゲートに順次アクセスできるようにする。

試しに作ったもの

大学の学部生と修士生の履歴書のデータをボタン一つで一括表示するような画面を作りました。

IteratorSampleApp.png

※長くなるので最後の方切れてます。
IteratorSampleAppResult.png

クラス図

Iteratorには各要素にアクセスできるようなメソッドを定義します。
ひとまず、次の要素を返すnext()と次の要素があるかを調べるhasNext()を定義しています。
必要によっては、一番最初の要素を返すfirst()や逆に最後の要素を返すlast()とかを定義しても良さそうです。

Iteratorが全てのアグリゲートに統一した方法でアクセスする方法を定義すれば、アグリゲートの形式を考える必要がなくなります。

また、新しいAggregateを作ってもそれ用のIteratorを追加し、同様にViewControllerでこれまでのAggregateと同様に呼び出せば良いので改修するのも簡単にできます。

ちなみに今回はModelを定義していますが、必ずしもIteratorパターンに必要なものではありません。

Iterator.png

実装

Modelの定義
protocol StudentModel {
    /// 氏名
    var name: String { get }
    /// 年齢
    var age: Int { get }
    /// 大学名
    var universityName: String { get }
    /// 志望動機
    var motivation: String { get }
}

// Masterの場合も同様
struct BachelorModel: StudentModel {
    /// 氏名
    let name: String
    /// 年齢
    let age: Int
    /// 大学名
    let universityName: String
    /// 志望動機
    let motivation: String
}
Aggregate周り
protocol Aggregate {
    /// イテレーターを作成
    func createIterator() -> Iterator
}

// Masterも同様なものを作成する
class Bachelor: Aggregate {
    var models = [BachelorModel]()

    init() {
        models.append(BachelorModel(name: "山田",
                                    age: 22,
                                    universityName: "〇〇大学",
                                    motivation: "頑張ります!"))
        // 同様にいくつか追加 
    }

    func createIterator() -> Iterator {
        return BachelorIterator(bachelor: self)
    }
}
Iterator周り
protocol Iterator {
    /// 次の要素があるか
    func hasNext() -> Bool
    /// 次の要素を取得する
    func next() -> StudentModel?
}

// Masterの場合も同様
class BachelorIterator: Iterator {
    private var bachelor: Bachelor?
    private var index = 0

    init(bachelor: Bachelor) {
        self.bachelor = bachelor
    }

    func hasNext() -> Bool {
        guard let models = bachelor?.models else {
            return false
        }
        return !(index >= models.count)
    }

    func next() -> StudentModel? {
        guard let models = bachelor?.models else {
            return nil
        }

        let bachelor = models[index]
        index += 1
        return bachelor
    }
}
IteratorViewController.swift
class IteratorViewController: UIViewController {
    /// 学部生
    private let bachelor = Bachelor()
    /// 修士生
    private let master = Master()

    @IBAction func didTapListUpButton(_ sender: Any) {
        listUpStudents()
    }

    /// 学部生と修士学生をリストアップ
    func listUpStudents() {
        let bachlorIterator = bachelor.createIterator()
        let masterIterator = master.createIterator()
        listUpStudents(from: bachlorIterator)
        listUpStudents(from: masterIterator)
    }

    /// 学生をリストアップ
    func listUpStudents(from iterator: Iterator) {
        while iterator.hasNext() {
            guard let model = iterator.next() else {
                return
            }

            print(model.name + " " + String(model.age) + "歳です。\n")
            print(model.universityName + "出身です。\n")
            print(model.motivation)
            print("==============")
        }
    }
}

Compositeパターン

概要

オブジェクトをツリー構造に構成して扱う。
このとき、クライアントは個別のオブジェクトオブジェクトのコンポジションを同じものとして扱うことができる

登場人物は以下の3つ

  • Component:コンポジションの全てのオブジェクト用のインターフェースを定義する
  • Composite:子要素を持つコンポーネントの振る舞いを定義し、子要素を格納する
  • Leaf:子要素を持たない要素。コンポジション内の全ての要素の振る舞いを実装する

試しに作ったもの

ツリー構造になりそうなもので浮かんだのが家系図だったので、家系図っぽくなるような家族のリストアップを行う画面を作りました。
画面自体には何かを表示すると言うことはせず、画面を開いた時に家族の一覧をコンソールに表示するようなものとなります。

田中家は通常パターンで家族-メンバーの関係で表示するようにしています。
鈴木家は、佐藤家から婿入りしてきたメンバーを盛り込んでいるので、階層化されています。
CompositeSampleAppResult.png

正直作った時にもっとわかりやすい例があったなと後悔...

クラス図

CompositeViewControllerがクライアントとしてComponentにアクセスします。
クライアントは、Family(Composite)にも、Person(Leaf)にもアクセスすることができます。

Componentをインターフェースとして定義し、Family、Personの両方でComponentを実装します。
CompositeであるFamilyの方では、listUp()を再起的に呼び出すことで、要素がLeafであればLeaf用のリストアップを行い、要素が再度Compositeであれば再度メソッドを呼び出すようにします。

Composite.png

実装

Component
protocol Component {
    func listUp()
}
CompositeとLeaf
/// 家系(Compositeに該当する)
class Family: Component {
    /// 全ての家族
    var family = [Component]()
    /// 名字
    private let familyName: String

    init(familyName: String) {
        self.familyName = familyName
    }

    func listUp() {
        print("\(familyName)\n")

        for person in family {
            person.listUp()
        }
    }
}

/// 個人(leafに該当する)
class Person: Component {
    /// 氏名
    private let name: String

    init(name: String) {
        self.name = name
    }

    func listUp() {
        print(name)
    }
}
CompositeViewController.swift
class CompositeViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        setupFamily()
    }

    /// 家族の設定
    func setupFamily() {
        let tanakaFamily = Family(familyName: "田中")
        tanakaFamily.family.append(Person(name: "田中一郎"))
        tanakaFamily.family.append(Person(name: "田中二郎"))
        tanakaFamily.family.append(Person(name: "田中三郎"))

        let suzukiFamily = Family(familyName: "鈴木")
        suzukiFamily.family.append(Person(name: "鈴木一花"))
        suzukiFamily.family.append(Person(name: "鈴木ニ菜"))
        suzukiFamily.family.append(Person(name: "鈴木三春"))
        // 婿入り
        let satoFamily = Family(familyName: "佐藤(旧姓)")
        satoFamily.family.append(Person(name: "佐藤一"))
        suzukiFamily.family.append(satoFamily)

        tanakaFamily.listUp()
        print("=============")
        suzukiFamily.listUp()
        print("=============")
    }
}

Stateパターン

概要

オブジェクトの内部状態が変化した時、オブジェクトがその振る舞いを変更することができる。
状態そのものをクラスとすることで、if分やswitch分の分岐を追加する必要がなくなる。

試しに作ったもの

車を想定した画面を作ってみました。
パーキングバックドライブのモードがあり、それぞれの状態でブレーキ、アクセルを押した時のリアクションが変わります。

モードのボタンを押すと、押したボタンが黄色になります。

Composite.png

それぞれの状態で、アクセルブレーキの順にボタンを押した時の状態です。

パーキング状態 ドライブ状態 バック状態
parkingState.png RunningState.png BackState.png

クラス図

Stateは全ての状態クラスの共通プロトコルで、全クラスで同じインターフェースを実装するので交換が可能になる。
ここではStateViewControllerが俗に言うContextと呼ばれる、多数の内部状態を持つクラスになる。

State.png

実装

State
protocol State {
    /// アクセル
    func run()
    /// ブレーキ
    func stop()
}

// Back, Runningの場合も同様
class ParkingState: State {
    func run() {
        print("動きません")
    }

    func stop() {
        print("既に停止しています")
    }
}
StateViewController.swift
class StateViewController: UIViewController {
    /// 停車状態
    private let parkingState = ParkingState()
    /// 戻る状態
    private let backState = BackState()
    /// 走行状態
    private let runningState = RunningState()

    /// 状態フラグ(初期値は停車状態)
    private var state: State = ParkingState()

    override func viewDidLoad() {
        super.viewDidLoad()

        setup()
    }

    // 各モードの切り替え

    @IBAction func didTapParkingButton(_ sender: Any) {
        setState(with: parkingState)

        // ボタンの色状態を変える処理は省略
    }

    @IBAction func didTapBackButton(_ sender: Any) {
        setState(with: backState)
    }

    @IBAction func didTapDriveButton(_ sender: Any) {
        setState(with: runningState)
    }

    // 各アクション

    @IBAction func didTapBreakButton(_ sender: Any) {
        state.stop()
    }

    @IBAction func didTapRunButton(_ sender: Any) {
        state.run()
    }

    /// 準備
    func setup() {
        parkingButton.backgroundColor = .yellow
        setState(with: parkingState)
    }

    /// 状態をセット
    /// - Parameter state: State
    func setState(with state: State) {
        self.state = state
    }
}

Compoundパターン

概要

デザインパターンを複数組み合わせたパターン。

書籍以外に参考にできるものはないかを検索してもなかなか出てこなかった。

どうやらデザインパターンを組み合わせてアーキテクチャにしたものといった表現もあり、MVCも一種のCompoundパターンのようです。

作ったもの

オリジナルのものを考えるのが大変だったので、参考書籍の部分からSwiftでも実装できそうな部分をなるべく抽出して作成しました。
DJの操作画面みたいなもので、BPMの調整ができます。

CompoundSampleApp.png

クラス図

必要そうな部分だけ抜き取りました。

View:画面の構成をする。画面の各パーツの状態を監視し、状態によって処理を変えたりする。
Controller:Viewでのユーザの操作/入力を受け取り、Modelにとってどのような意味を持つかを解釈してModelに渡す。
Model:画面の状態やデータを取り扱う。Controllerから受け取った状態によって値を更新し、更新後はViewのオブザーバーに通知を送る。

監視通知といっているので、Observerパターンを利用していることはわかりやすいかと思います。

また、今回の例ではあまり恩恵を得られていないかもしれませんが、DJViewControllerInterfaceを参照していますが、これによってBPMControllerではない別のControllerを使いたいとなった時に交換が容易になります。
これは俗に言うStrategyパターンを用いています。

Compound.png

実装

全部載せようとすると長くなりそうなのでリポジトリからコードをご覧いただければと思います。

おさらい

パターン 特徴
Iterator 集約したオブジェクトにアクセスして、読み込みや操作可能にする
Composite オブジェクトをツリー構造にして扱うことで、呼び出す側で親/子を気にしないで呼び出せる
State オブジェクトの状態をクラスにすることで、オブジェクトが振る舞いを変更可能にする
Compound デザインパターンを組み合わせたパターン

今回も4つのデザインパターンについて学びました。
今回のデザインパターンもGitHubのリポジトリで公開していますので、具体的な実装をみたい方がいらっしゃったらご覧ください。
今回のパターンはPatterns3のディレクトリ内に含まれています。

ここまで長かった...
デザインパターンって多いし、中にはこれなんか似てね?みたいなのもあるかと思います。

引き続き、本記事でもご指摘やアドバイス等があればぜひコメントをいただきたく思います。
いただいたコメントを踏まえて、学びをより深いものにして行けたらと思っております✨

一旦デザインパターンを学ぶシリーズはここまでにしようと思います。
参考書籍にはおまけで他のパターンもありましたが、そちらはぜひご自分の目でご確認いただければと思います。

これまで読んでくださった方も、これだけ読んだよという方も、この記事にたどり着いてくださりありがとうございました。

参照

共通
Head Firstデザインパターン ―頭とからだで覚えるデザインパターンの基本

Iteratorパターン
デザインパターン「Iterator」 - Qiita

Compositeパターン
Swiftで学ぶデザインパターン13 (Compositeパターン) - しめ鯖日記

Stateパターン
State – 野生のプログラマZ

Compoundパターン

  1. 【デザインパターン】コンパウンドパターン【事前学習編1】
  2. 【Swift】MVCについて分かりやすいサンプルが無かったのでSwiftでMVCモデルで簡易アプリ書いてみた【MVCパターン】
2
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
5