はじめに
Swiftで覚えるデザインパターン第3弾です。
- Swiftで覚えるデザインパターン
- Strategy
- Observer
- Decorator
- Factory Method
- Abstract Factory
- Singleton
- Swiftで覚えるデザインパターンその2
- Command
- Adaptor
- Facade
- Template Method
-
Swiftで覚えるデザインパターンその3 ← 今ココ
- Iterator
- Composite
- State
- Compound
Iteratorパターン
概要
集約したいくつかのオブジェクト(配列やコレクション) = アグリゲート
に順次アクセスできるようにする。
試しに作ったもの
大学の学部生と修士生の履歴書のデータをボタン一つで一括表示するような画面を作りました。
クラス図
Iterator
には各要素にアクセスできるようなメソッドを定義します。
ひとまず、次の要素を返すnext()
と次の要素があるかを調べるhasNext()
を定義しています。
必要によっては、一番最初の要素を返すfirst()
や逆に最後の要素を返すlast()
とかを定義しても良さそうです。
Iteratorが全てのアグリゲートに統一した方法でアクセスする方法を定義すれば、アグリゲートの形式を考える必要がなくなります。
また、新しいAggregateを作ってもそれ用のIteratorを追加し、同様にViewControllerでこれまでのAggregateと同様に呼び出せば良いので改修するのも簡単にできます。
ちなみに今回はModelを定義していますが、必ずしもIteratorパターンに必要なものではありません。
実装
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
}
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)
}
}
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
}
}
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
:子要素を持たない要素。コンポジション内の全ての要素の振る舞いを実装する
試しに作ったもの
ツリー構造になりそうなもので浮かんだのが家系図だったので、家系図っぽくなるような家族のリストアップを行う画面を作りました。
画面自体には何かを表示すると言うことはせず、画面を開いた時に家族の一覧をコンソールに表示するようなものとなります。
田中家は通常パターンで家族-メンバー
の関係で表示するようにしています。
鈴木家は、佐藤家から婿入りしてきたメンバーを盛り込んでいるので、階層化されています。
正直作った時にもっとわかりやすい例があったなと後悔...
クラス図
CompositeViewController
がクライアントとしてComponentにアクセスします。
クライアントは、Family(Composite)
にも、Person(Leaf)
にもアクセスすることができます。
Componentをインターフェースとして定義し、Family、Personの両方でComponentを実装します。
CompositeであるFamilyの方では、listUp()
を再起的に呼び出すことで、要素がLeafであればLeaf用のリストアップを行い、要素が再度Compositeであれば再度メソッドを呼び出すようにします。
実装
protocol Component {
func listUp()
}
/// 家系(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)
}
}
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分の分岐を追加する必要がなくなる。
試しに作ったもの
車を想定した画面を作ってみました。
パーキング
、バック
、ドライブ
のモードがあり、それぞれの状態でブレーキ、アクセルを押した時のリアクションが変わります。
モードのボタンを押すと、押したボタンが黄色になります。
それぞれの状態で、アクセル
→ブレーキ
の順にボタンを押した時の状態です。
パーキング状態 | ドライブ状態 | バック状態 |
---|---|---|
クラス図
State
は全ての状態クラスの共通プロトコルで、全クラスで同じインターフェースを実装するので交換が可能になる。
ここではStateViewController
が俗に言うContext
と呼ばれる、多数の内部状態を持つクラスになる。
実装
protocol State {
/// アクセル
func run()
/// ブレーキ
func stop()
}
// Back, Runningの場合も同様
class ParkingState: State {
func run() {
print("動きません")
}
func stop() {
print("既に停止しています")
}
}
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の調整ができます。
クラス図
必要そうな部分だけ抜き取りました。
View
:画面の構成をする。画面の各パーツの状態を監視し、状態によって処理を変えたりする。
Controller
:Viewでのユーザの操作/入力を受け取り、Modelにとってどのような意味を持つかを解釈してModelに渡す。
Model
:画面の状態やデータを取り扱う。Controllerから受け取った状態によって値を更新し、更新後はViewのオブザーバーに通知を送る。
監視や通知といっているので、Observerパターン
を利用していることはわかりやすいかと思います。
また、今回の例ではあまり恩恵を得られていないかもしれませんが、DJView
がControllerInterface
を参照していますが、これによってBPMControllerではない別のControllerを使いたいとなった時に交換が容易になります。
これは俗に言うStrategyパターン
を用いています。
実装
全部載せようとすると長くなりそうなのでリポジトリからコードをご覧いただければと思います。
おさらい
パターン | 特徴 |
---|---|
Iterator | 集約したオブジェクトにアクセスして、読み込みや操作可能にする |
Composite | オブジェクトをツリー構造にして扱うことで、呼び出す側で親/子を気にしないで呼び出せる |
State | オブジェクトの状態をクラスにすることで、オブジェクトが振る舞いを変更可能にする |
Compound | デザインパターンを組み合わせたパターン |
今回も4つのデザインパターンについて学びました。
今回のデザインパターンもGitHubのリポジトリで公開していますので、具体的な実装をみたい方がいらっしゃったらご覧ください。
今回のパターンはPatterns3のディレクトリ内に含まれています。
ここまで長かった...
デザインパターンって多いし、中にはこれなんか似てね?みたいなのもあるかと思います。
引き続き、本記事でもご指摘やアドバイス等があればぜひコメントをいただきたく思います。
いただいたコメントを踏まえて、学びをより深いものにして行けたらと思っております✨
一旦デザインパターンを学ぶシリーズはここまでにしようと思います。
参考書籍にはおまけで他のパターンもありましたが、そちらはぜひご自分の目でご確認いただければと思います。
これまで読んでくださった方も、これだけ読んだよという方も、この記事にたどり着いてくださりありがとうございました。
参照
共通
Head Firstデザインパターン ―頭とからだで覚えるデザインパターンの基本
Iteratorパターン
デザインパターン「Iterator」 - Qiita
Compositeパターン
Swiftで学ぶデザインパターン13 (Compositeパターン) - しめ鯖日記
Stateパターン
State – 野生のプログラマZ
Compoundパターン