3
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2021-08-18

はじめに

前回投稿したSwiftで覚えるデザインパターンの続きです。

  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 2021/09/09公開
  4. Iterator
  5. Composite
  6. State
  7. Compound

Commandパターン

概要

リクエスト(操作)を一つのオブジェクトとしてカプセル化する。
これにより、操作の追加が容易になったり、操作を組み合わせて新しい操作ができる。

大きく以下の4つから成る。

  • 具体的なCommandを作成し、そのレシーバの設定をするClient
  • リクエストに対応するための処理について知っているReceiver
  • アクションとレシーバを結びつけ、レシーバの処理を実行するCommand
  • コマンドを保持し、呼び出されたコマンドを実行するように依頼するInvoker

試しに作ったもの

某有名ブラザーズゲームっぽいコントローラー画面を作りました。

お兄ちゃんは、Aを押せばジャンプ、Bを押せばファイアボールをします。
弟くんはAとBで逆のアクションをする変わり者です。

また、お兄ちゃんの方だけ特別にCを押せばジャンプしながらファイアボールをしてくれます。

CommandSampleApp.png
Aを押した時 Bを押した時 Cを押した時
JumpAction.png FireballAction.png SpecialCommand.png

クラス図

今回のViewControllerがClient役になり、Invoker/具体的なレシーバ/具体的なコマンドを呼ぶ。
InvokerCommandを呼び出し、格納、削除、リクエストされた時点でのコマンド実行依頼を行う。
ReceiverはMario、Luigi内にリクエストに対する具体的なアクションを実装していく。

具体的なCommandで、レシーバのアクションを実行するように処理を記述する。
Commandが呼び出すレシーバによって処理が変更できるし、レシーバが具体的な処理を知っているので他のオブジェクトがそれを知っている必要はなくなる。

Command.png

実装

Receiver周り
protocol Receiver {
    var name: String { get }

    func action(by commandType: CommandType)
}

// ルイージの場合も同様
class Mario: Receiver {
    var name: String = "マリオ"
    
    func action(by commandType: CommandType) {
        let commandText: String
        switch commandType {
        case .A:
            commandText = "プーン"
        case .B:
            commandText = "ボッ"
        }
        print("\(name)\(commandText)」")
    }
}
Command周り
protocol Command {
    var receiver: Receiver { get }

    func execute()
}

/// コマンドの種類
enum CommandType {
    /// Aボタン
    case A
    /// Bボタン
    case B
}

// 他のコマンドも同様
class JumpCommand: Command {
    var receiver: Receiver

    init(receiver: Receiver) {
        self.receiver = receiver
    }

    func execute() {
        receiver.action(by: .A)
    }
}
Invoker
class CommandInvoker {
    /// コマンド配列
    private var commands: [Command] = []

    /// コマンドを追加する
    func addCommand(with command: Command) {
        commands.append(command)
    }

    /// コマンドを全て削除する
    func removeAllCommands() {
        commands.removeAll()
    }

    /// コマンドを実行する
    func execute() {
        for command in commands {
            command.execute()
        }
    }
}
CommandViewController.swift
class CommandViewController: UIViewController {
    /// Invoker
    private let invoker = CommandInvoker()
    /// Receivers
    private let mario = Mario()
    private let luigi = Luigi()

    // 他のボタンを押した時も同様
    @IBAction func didTapJumpButton(_ sender: Any) {
        let marioJumpCommand = JumpCommand(receiver: mario)
        let luigiJumpCommand = JumpCommand(receiver: luigi)
        action(with: marioJumpCommand)
        action(with: luigiJumpCommand)
    }

    // コマンドの組み合わせを行なっている
    @IBAction func didTapSpecialCommand(_ sender: Any) {
        let jump = JumpCommand(receiver: mario)
        let fireball = FireballCommand(receiver: mario)
        action(with: jump)
        action(with: fireball)
    }

    /// コマンドを実行する
    func action(with command: Command) {
        invoker.addCommand(with: command)
        invoker.execute()
        // 別のコマンドを実行したときのため登録したコマンドを削除しておく
        invoker.removeAllCommands()
    }
}

Adaptorパターン

概要

クライアントが必要しているがそのままだと使えないクラスのインターフェースを、別のインターフェースに変換することで利用可能にする。
この変換する役割を持っているものがアダプタである。

クライアントをインターフェースから分離するのでアダプタがインターフェースの変更をカプセル化して、クライアントの変更を不要にする。

また、アダプタには下記の2種類がある。

  • オブジェクトアダプタ
  • クラスアダプタ

試しに作ったもの

野球選手の、ピッチングとバッティングの動作を、ボタンを押したら表示するようなものを作りました。

AdaptorSampleApp.png
強打者を押した時 エースを押した時
Hitter.png Pitcher.png

クラス図

動作自体は同じように見えますが、

強打者:オブジェクトアダプター
エースピッチャークラスアダプター

で作成しています。

オブジェクトアダプタはリクエストをアダプティに移譲します。
クラスアダプタは継承を用いることで、アダプターはアダプティのサブクラスになります。

Javaのように多重継承を使えない言語ではオブジェクトアダプタを使うしかなさそうですね。

Adaptor.png

実装

BaseballProtocol.swift
protocol BaseballPlayer {
    func throwing()
    func hitting()
}
ObjectAdaptor
class PowerHitter {
    func homerun() {
        print("ホームラン")
    }
}

class PowerHitterAdaptor: BaseballPlayer {
    let player: PowerHitter

    init(player: PowerHitter) {
        self.player = player
    }

    func throwing() {
        print("送球")
    }

    func hitting() {
        player.homerun()
    }
}
ClassAdaptor
class AcePitcher {
    /// 投球
    func fastBall() {
        print("豪速球")
    }
}

// Adaptor
class AcePitcherAdaptor: AcePitcher, BaseballPlayer {
    func throwing() {
        super.fastBall()
    }

    func hitting() {
        print("打撃")
    }
}
AdaptorViewController.swift
class AdaptorViewController: UIViewController {

    let powerHitter = PowerHitter()

    var pitcherAdaptor: BaseballPlayer?
    var hitterAdaptor: BaseballPlayer?

    override func viewDidLoad() {
        super.viewDidLoad()

        hitterAdaptor = PowerHitterAdaptor(player: powerHitter)
        pitcherAdaptor = AcePitcherAdaptor()
    }

    // throwingButtonも同様
    @IBAction func didTapHittingButton(_ sender: Any) {
        guard let adaptor = hitterAdaptor else {
            return
        }
        adaptor.hitting()
        adaptor.throwing()
    }
}

Facadeパターン

概要

複雑になったサブシステムを簡素化して統合するクラスを作成することで、クライアント側で呼びやすくする。

ちなみにFacadeの読み方はファサード。ファケードとかかと思っていた。
Facadeは(建物の)正面、(しばしば実体よりりっぱな事物の)外見、見た目みたいな意味があるみたいです。

つまり、細かい処理をしているサブシステムたちの処理をファサードに集約させて、クライアントは窓口(ファサード)に処理をお願いするようにすればシンプルな実装になり、クライアントとサブシステムが密結合になるのを防ぐことができます。

試しに作ったもの

家主が外出中は家電類の電源を切り、帰宅したら全ての家電の電源を入れるような画面を作りました。
中央のスイッチの切り替えで外出/帰宅のステータスを切り替えます。

FacadeSampleApp.png
外出中 帰宅時
Outsider.png Backhome.png

クラス図

今回はスマートスピーカーをFacade代わりにしています。(本来であれば人感センサーとかの方がここでは適切だったりする?笑)
スマートスピーカーが家電系の操作諸々の処理を集約してくれているので、クライアントはサブシステムの処理を知っている必要はありません。

Adaptor.png

実装

サブシステム周り
/// 家電の電源On/Off操作プロトコル
protocol HomeAppliances {
    /// 電源をつける
    static func turnOn()
    /// 電源を切る
    static func turnOff()
}

/// TV(照明やスピーカーも同様)
class TV: HomeAppliances {
    static func turnOn() {
        print("TVをつけました")
    }

    static func turnOff() {
        print("TVを消しました")
    }
}
Facade
/// スマートスピーカー(Facadeの役割)
class SmartSpeaker {
    /// 全ての家電の電源をつける
    func turnOnAllHomeAppliances() {
        Light.turnOn()
        TV.turnOn()
        Speaker.turnOn()
    }

    /// 全ての家電の電源を切る
    func turnOffAllHomeAppliances() {
        Light.turnOff()
        TV.turnOff()
        Speaker.turnOff()
    }
}
FacadeViewController.swift
class FacadeViewController: UIViewController {
    /// スマートスピーカー
    private let smartSpeaker = SmartSpeaker()
    /// 家主のステータスを操作するスイッチ
    @IBOutlet private weak var toggleSwitch: UISwitch!

    @IBAction func didToggleSwitch(_ sender: Any) {
        if toggleSwitch.isOn {
            smartSpeaker.turnOnAllHomeAppliances()
        } else {
            smartSpeaker.turnOffAllHomeAppliances()
        }
    }
}

Template Methodパターン

概要

メソッド内のアルゴリズムの骨組みを定義し、その手順のいくつかをサブクラス側で再定義させる。

これにより得られる恩恵としては以下が考えられます。

  • 大元のアルゴリズムを変更する必要がなくなる。
  • 同様のクラスを作成する場合は「いくつかの手順」のみを定義するだけで良くなる。

試しに作ったもの

二人の新卒社員による自己紹介をしてもらう画面です。
ボタンを押せばそれぞれのあいさつを確認できます。

TemplateMethodSampleApp.png
田中さん 山田さん
Tanaka.png Yamada.png

クラス図

SelfIntroductionがJavaで言うAbstract Class(抽象クラス)に当たりますが、Swiftには抽象クラスを定義できないので、protocol extensionを用いて実現させます。

テンプレート部分の抽象メソッドは具象クラス(Tanaka, Yamada)で実装することでそれぞれの自己紹介を作成するようにします。

TemplateMethod.png

実装

テンプレート
protocol SelfIntroduction {
    /// 名前
    func name()
    /// 職業
    func job()
}

extension SelfIntroduction {
    /// 自己紹介
    func introduction() {
        greet()
        name()
        job()
        enthusiasm()
    }

    /// 最初の一言
    func greet() {
        print("こんにちは!")
    }

    /// 意気込み
    func enthusiasm() {
        print("頑張ります!よろしくお願い致します。")
    }
}
各社員
// 山田さんの場合も同様
class Tanaka: SelfIntroduction {
    func name() {
        print("田中です!")
    }

    func job() {
        print("エンジニアやってます。")
    }
}
TemplateMethodViewController.swift
class TemplateMethodViewController: UIViewController {
    /// 田中さん
    private let tanaka = Tanaka()
    /// 山田さん
    private let yamada = Yamada()

    @IBAction func didTapTanakaButton(_ sender: Any) {
        tanaka.introduction()
    }

    @IBAction func didTapYamadaButton(_ sender: Any) {
        yamada.introduction()
    }
}

おさらい

パターン 特徴
Command リクエスト(操作)を一つのオブジェクトとして作成する。 これにより高い拡張性や再利用性を担保できる。
Adaptor クライアントが使えないインターフェースを扱えるインターフェースの形に変換する。
Facade 複雑なサブシステムの処理を一つのインターフェースに統合する。これによりクライアントはインターフェースを呼び出すだけで良くなる。
Template Method 骨組みの決まったアルゴリズムの内の「ある手順」をサブクラスに再定義させる。 アルゴリズムの大きな構造を変えずに細かい処理を修正できるようになる。

今回は4つのデザインパターンについて学びました。
実際に作ったアプリ(クラス図画像付き)は前回の記事同様、GitHubのリポジトリで公開していますので、具体的な実装をみたい方がいらっしゃったらご覧ください。
投稿したQiitaの記事ごとにディレクトリを分けているので、今回の実装はPatterns2に含まれております。

Facadeパターンとかは結構シンプルで覚えやすいですが、CommandパターンやAdaptorパターンは地味に複雑な構造という印象を受けたので使わないと忘れてしまいそうですね(汗

ここ違くないか?みたいな指摘やこうすれば覚えやすいよみたいなアドバイス等があればぜひコメントをいただきたく思います。
いただいたコメントを踏まえて、学びをより深いものにして行けたらと思っております✨

参照

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

Commandパターン

  1. Command – 野生のプログラマZ
  2. 【Commandパターン】GUIイベント処理や履歴管理で用いるデザインパターン【コード例:Java】

Adaptorパターン

  1. Swiftで学ぶデザインパターン9 (Adapterパターン) - しめ鯖日記
  2. Swiftを使用してデザインパターンまとめ 2.Adapterパターン

Facadeパターン

  1. Facade – 野生のプログラマZ
  2. [15.Facadeパターン | TECHSCORE(テックスコア)] (https://www.techscore.com/tech/DesignPattern/Facade.html/)

Template Methodパターン
【Swiftでデザインパターン】TemplateMethod - Qiita

3
6
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
3
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?