はじめに
前回投稿したSwiftで覚えるデザインパターンの続きです。
-
Swiftで覚えるデザインパターン
- Strategy
- Observer
- Decorator
- Factory Method
- Abstract Factory
- Singleton
-
Swiftで覚えるデザインパターンその2 ← 今ココ
- Command
- Adaptor
- Facade
- Template Method
-
Swiftで覚えるデザインパターンその3
2021/09/09公開
- Iterator
- Composite
- State
- Compound
Commandパターン
概要
リクエスト(操作)を一つのオブジェクトとしてカプセル化する。
これにより、操作の追加が容易になったり、操作を組み合わせて新しい操作ができる。
大きく以下の4つから成る。
- 具体的なCommandを作成し、そのレシーバの設定をする
Client
- リクエストに対応するための処理について知っている
Receiver
- アクションとレシーバを結びつけ、レシーバの処理を実行する
Command
- コマンドを保持し、呼び出されたコマンドを実行するように依頼する
Invoker
試しに作ったもの
某有名ブラザーズゲームっぽいコントローラー画面を作りました。
お兄ちゃんは、Aを押せばジャンプ、Bを押せばファイアボールをします。
弟くんはAとBで逆のアクションをする変わり者です。
また、お兄ちゃんの方だけ特別にCを押せばジャンプしながらファイアボールをしてくれます。
Aを押した時 | Bを押した時 | Cを押した時 |
---|---|---|
クラス図
今回のViewControllerがClient
役になり、Invoker/具体的なレシーバ/具体的なコマンドを呼ぶ。
Invoker
はCommand
を呼び出し、格納、削除、リクエストされた時点でのコマンド実行依頼を行う。
Receiver
はMario、Luigi内にリクエストに対する具体的なアクションを実装していく。
具体的なCommand
で、レシーバのアクションを実行するように処理を記述する。
Commandが呼び出すレシーバによって処理が変更できるし、レシーバが具体的な処理を知っているので他のオブジェクトがそれを知っている必要はなくなる。
実装
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)」")
}
}
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)
}
}
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()
}
}
}
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種類がある。
- オブジェクトアダプタ
- クラスアダプタ
試しに作ったもの
野球選手の、ピッチングとバッティングの動作を、ボタンを押したら表示するようなものを作りました。
強打者を押した時 | エースを押した時 |
---|---|
クラス図
動作自体は同じように見えますが、
強打者:オブジェクトアダプター
エースピッチャークラスアダプター
で作成しています。
オブジェクトアダプタはリクエストをアダプティに移譲します。
クラスアダプタは継承を用いることで、アダプターはアダプティのサブクラスになります。
Javaのように多重継承を使えない言語ではオブジェクトアダプタを使うしかなさそうですね。
実装
protocol BaseballPlayer {
func throwing()
func hitting()
}
class PowerHitter {
func homerun() {
print("ホームラン")
}
}
class PowerHitterAdaptor: BaseballPlayer {
let player: PowerHitter
init(player: PowerHitter) {
self.player = player
}
func throwing() {
print("送球")
}
func hitting() {
player.homerun()
}
}
class AcePitcher {
/// 投球
func fastBall() {
print("豪速球")
}
}
// Adaptor
class AcePitcherAdaptor: AcePitcher, BaseballPlayer {
func throwing() {
super.fastBall()
}
func hitting() {
print("打撃")
}
}
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は(建物の)正面、(しばしば実体よりりっぱな事物の)外見、見た目みたいな意味があるみたいです。
つまり、細かい処理をしているサブシステムたちの処理をファサードに集約させて、クライアントは窓口(ファサード)に処理をお願いするようにすればシンプルな実装になり、クライアントとサブシステムが密結合になるのを防ぐことができます。
試しに作ったもの
家主が外出中は家電類の電源を切り、帰宅したら全ての家電の電源を入れるような画面を作りました。
中央のスイッチの切り替えで外出/帰宅のステータスを切り替えます。
外出中 | 帰宅時 |
---|---|
クラス図
今回はスマートスピーカーをFacade代わりにしています。(本来であれば人感センサーとかの方がここでは適切だったりする?笑)
スマートスピーカーが家電系の操作諸々の処理を集約してくれているので、クライアントはサブシステムの処理を知っている必要はありません。
実装
/// 家電の電源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の役割)
class SmartSpeaker {
/// 全ての家電の電源をつける
func turnOnAllHomeAppliances() {
Light.turnOn()
TV.turnOn()
Speaker.turnOn()
}
/// 全ての家電の電源を切る
func turnOffAllHomeAppliances() {
Light.turnOff()
TV.turnOff()
Speaker.turnOff()
}
}
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パターン
概要
メソッド内のアルゴリズムの骨組みを定義し、その手順のいくつかをサブクラス側で再定義させる。
これにより得られる恩恵としては以下が考えられます。
- 大元のアルゴリズムを変更する必要がなくなる。
- 同様のクラスを作成する場合は「いくつかの手順」のみを定義するだけで良くなる。
試しに作ったもの
二人の新卒社員による自己紹介をしてもらう画面です。
ボタンを押せばそれぞれのあいさつを確認できます。
田中さん | 山田さん |
---|---|
クラス図
SelfIntroduction
がJavaで言うAbstract Class(抽象クラス)
に当たりますが、Swiftには抽象クラスを定義できないので、protocol extensionを用いて実現させます。
テンプレート部分の抽象メソッドは具象クラス(Tanaka, Yamada)で実装することでそれぞれの自己紹介を作成するようにします。
実装
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("エンジニアやってます。")
}
}
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パターン
Adaptorパターン
Facadeパターン
- Facade – 野生のプログラマZ
- [15.Facadeパターン | TECHSCORE(テックスコア)] (https://www.techscore.com/tech/DesignPattern/Facade.html/)
Template Methodパターン
【Swiftでデザインパターン】TemplateMethod - Qiita