GoFデザインパターンをSwiftyに書く
あけましておめでとうございます
年末年始休みで雪国のど田舎に帰省してゆっくりしてますが、コタツの中であまりに暇なので腰を据えて設計について考えるいい機会だなと思い、このテーマを選びました。
この記事で伝えたいこと
GoF(ゴフ)デザインパターンとは、the Gang of Fourと呼ばれる4人の開発者によって整理された、オブジェクト指向にもとづいた23個のプログラムのパターンのことです。
この記事では、その23個のパターンの中でもよく使いそうなものについてはサンプルコードをつけて紹介してます。その他のパターンは概要の説明にとどめてます。サンプルではできるだけ身近な例を使っているので、GoFデザインパターンにおける「オブジェクトの役割分担」と「プロトコルや構造体なども使ったSwiftらしい実装」のイメージをサクッとつかむ手助けになればこの記事は成功かなと思ってます。
ちなみに、概要説明で頻繁に出てくる「オブジェクト」という表現はSwiftのクラスや構造体などを指しています。
目次
-
振る舞いに関するパターン
-
生成に関するパターン
-
構造に関するパターン
振る舞いに関するパターン
Chain of Responsibility - 責任の連鎖
振る舞いに関するパターン
生成に関するパターン
構造に関するパターン
Chain of Responsibility - 責任の連鎖
ある処理をオブジェクトに渡す際、処理の内容に応じて対応するオブジェクトを変えます。
Command - 命令
ある処理やその処理に伴って変化するパラメータなどをオブジェクトとしてまとめます。
Interpreter - 通訳
コードを解析し、その結果に応じて処理を行うオブジェクトをつくります。
Iterator - 反復子
配列などの集合体に対して、その要素1つ1つに順番に処理を行います。
Mediator - 調停者
複数のオブジェクト間の相互作用を調整するオブジェクトをつくります。
Memento - 思い出
ある時点でのインスタンスの状態を保存したり、復元したりするオブジェクトをつくります。
Observer - 観察者
監視される側の状態が変化したときに監視する側に通知するオブジェクトをつくります。
State - 状態
状態をオブジェクトとしてまとめ、その変化によって処理を切り替えます。
サンプルでは、Feelings
というenumが状態オブジェクトに当たり、state
の変化によってEngineer
が振る舞いを変えます。
enum Feelings {
case well, tired
var isWell: Bool {
return self == .well
}
func todo(for worker: Worker) -> String {
switch self {
case .well: return worker.task
case .tired: return worker.relaxation
}
}
}
protocol Worker {
var task: String { get set }
var relaxation: String { get set }
var wantsToDoTask: Bool { get }
mutating func switchState(to state: Feelings)
func doSomething()
}
struct Engineer: Worker {
private var state = Feelings.well
var task: String
var relaxation: String
var wantsToDoTask: Bool {
return state.isWell
}
init(task: String, relaxation: String) {
self.task = task
self.relaxation = relaxation
}
mutating func switchState(to state: Feelings) {
self.state = state
}
func doSomething() {
print("\(state.todo(for: self))をする")
}
}
var shintykt = Engineer(task: "プログラミング", relaxation: "サウナ浴")
shintykt.wantsToDoTask // true
shintykt.doSomething() // プログラミングをする
shintykt.switchState(to: .tired)
shintykt.wantsToDoTask // false
shintykt.doSomething() // サウナ浴をする
Strategy - 戦略
アルゴリズムをまとめたオブジェクトを複数用意し、その選択によって処理を切り替えます。
サンプルでは、iOSCareer
かAndroidCareer
をEngineer
が選んで振る舞いを決めます。
protocol Career {
func getSkill()
}
struct iOSCareer: Career {
func getSkill() {
print("iOS開発スキルを身につける")
}
}
struct AndroidCareer: Career {
func getSkill() {
print("Android開発スキルを身につける")
}
}
struct Engineer {
private var career: Career
init(career: Career) {
self.career = career
}
func getSkill() {
career.getSkill()
}
}
let shintykt = Engineer(career: iOSCareer())
shintykt.getSkill() // iOS開発スキルを身につける
Template Method - テンプレートのメソッド
テンプレのメソッドを用意し、各オブジェクトで共有します。
サンプルでは、プロトコルのcontribute()
で共有すべき処理の流れを実装しておいて、write()
とpost()
で具体的に何をするかはプロトコルの実装先に任せてます。
protocol Contributor {
func write()
func post()
func contribute()
}
extension Contributor {
func contribute() {
(0 ..< 3).forEach { _ in write() }
post()
}
}
struct QiitaContributor: Contributor {
func write() {
print("Qiitaの記事を練る")
}
func post() {
print("Qiitaに投稿する")
}
}
let shintykt = QiitaContributor()
shintykt.contribute()
/* Qiitaの記事を練る
Qiitaの記事を練る
Qiitaの記事を練る
Qiitaに投稿する */
Visitor - 訪問者
データ構造と処理を分け、処理を担うオブジェクトが複数のデータ構造に対して順々に処理を行います。
サンプルでは、処理を担うProgrammer
とデータ構造を表すSwift
、PHP
、Dart
で分かれてます。
// 処理する側
protocol ProgrammingLanguageVisitor {
func study(_ language: Swift)
func study(_ language: PHP)
func study(_ language: Dart)
}
class Programmer: ProgrammingLanguageVisitor {
var currentLanguage: String?
func study(_ language: Swift) {
currentLanguage = "Swift"
}
func study(_ language: PHP) {
currentLanguage = "PHP"
}
func study(_ language: Dart) {
currentLanguage = "Dart"
}
}
// 処理される側
protocol ProgrammingLanguage {
func accept(_ programmer: ProgrammingLanguageVisitor)
}
class Swift: ProgrammingLanguage {
func accept(_ programmer: ProgrammingLanguageVisitor) {
programmer.study(self)
}
}
class PHP: ProgrammingLanguage {
func accept(_ programmer: ProgrammingLanguageVisitor) {
programmer.study(self)
}
}
class Dart: ProgrammingLanguage {
func accept(_ programmer: ProgrammingLanguageVisitor) {
programmer.study(self)
}
}
let shintykt = Programmer()
let languages = [Swift(), PHP(), Dart()].map { (language: ProgrammingLanguage) -> String in
language.accept(shintykt)
return shintykt.currentLanguage ?? ""
} // ["Swift", "PHP", "Dart"]
生成に関するパターン
Abstract Factory - 抽象的な製造所
あるインスタンスを生成するための処理やそれに関連するインスタンスの生成について定めたオブジェクトをつくります。
Builder - 建造者
ある複雑な構造をもったインスタンスを段階的に生成するための処理を定めたオブジェクトをつくります。
Factory Method - 製造所のメソッド
インスタンスの生成方法を定めたテンプレのメソッドを用意し、各オブジェクトで共有します。
サンプルでは、ImoniFan
プロトコルでインスタンスの生成方法を実装しておいて、PersonFromYamagata
ではじめてインスタンスの具体型を決めています。
ちなみに芋煮というのは里芋を使った東北の郷土料理のことで、牛肉×しょうゆ味の山形芋煮派と豚肉×みそ味の宮城芋煮派で分かれてます。
// 生成される側
protocol Imoni {
var ingredient: String { get }
var taste: String { get }
}
struct YamagataImoni: Imoni {
var ingredient: String {
return "牛肉"
}
var taste: String {
return "しょう油味"
}
}
struct MiyagiImoni: Imoni {
var ingredient: String {
return "豚肉"
}
var taste: String {
return "みそ味"
}
}
// 生成する側
enum Prefecture {
case Yamagata, Miyagi
}
protocol ImoniFan {
func makeImoni(from prefecture: Prefecture) -> Imoni
func eatImoni()
}
extension ImoniFan {
func makeImoni(from prefecture: Prefecture) -> Imoni {
switch prefecture {
case .Yamagata: return YamagataImoni()
case .Miyagi: return MiyagiImoni()
}
}
}
struct PersonFromYamagata: ImoniFan {
func eatImoni() {
let imoni = makeImoni(from: .Yamagata)
print("\(imoni.ingredient)の入った\(imoni.taste)の芋煮を食べる")
}
}
let shintykt = PersonFromYamagata()
shintykt.eatImoni() // 牛肉の入ったしょう油味の芋煮を食べる
Prototype - 原型
ひな形のインスタンスをコピーして類似したインスタンスをつくります。
Singleton - 唯一の存在
インスタンスが1個しか生成されないことを保証します。
サンプルでは、init()
のアクセスレベルをprivate
にすることでインスタンス生成を防いでいます。checker.hasSameInstance
でインスタンスが唯一であることがわかります。
class Singleton {
static let shared = Singleton()
private init() {
print("インスタンスが生成された")
}
}
class SingletonChecker {
private let firstInstance = Singleton.shared
private let secondInstance = Singleton.shared
var hasSameInstance: Bool {
return firstInstance === secondInstance
}
}
let checker = SingletonChecker() // インスタンスが生成された
checker.hasSameInstance // true
構造に関するパターン
Adapter - 適合させるもの
データ構造を扱いやすいようにラップして提供するオブジェクトをつくります。
Bridge - 橋渡し
機能を追加するためのオブジェクトと実装を追加するためのオブジェクトを分け、両者を組み合わせて使います。
Composite - 混合物
格納する側と格納される側を同じものとみなして再帰的な構造をつくります。
サンプルでは、Member
とTeam
を同じOrganizationEntry
とみなしています。Member
側ではOrganizationEntry
を追加することができないので、add(_ entry: OrganizationEntry)
でエラーにしたりします。
protocol OrganizationEntry {
var name: String { get }
var role: String { get }
mutating func add(_ entry: OrganizationEntry)
}
struct Member: OrganizationEntry {
let name: String
var role: String
init(name: String, role: String) {
self.name = name
self.role = role
}
func add(_ entry: OrganizationEntry) {
print("メンバーはエントリ追加不可")
}
}
struct Team: OrganizationEntry {
let name: String
var role: String
private var entryList = [OrganizationEntry]()
init(name: String, role: String) {
self.name = name
self.role = role
}
mutating func add(_ entry: OrganizationEntry) {
entryList.append(entry)
}
func showList() {
entryList.forEach { print($0.name) }
}
}
var iOSTeam = Team(name: "iOSチーム", role: "iOSアプリ開発・保守・運用")
let shintykt = Member(name: "shintykt", role: "iOSエンジニア")
let developmentTeam = Team(name: "開発チーム", role: "アプリ開発")
iOSTeam.add(shintykt)
iOSTeam.add(developmentTeam)
iOSTeam.showList()
/* shintykt
開発チーム */
Decorator - 装飾者
あるインスタンスに対して補足的な機能を追加していくオブジェクトをつくります。
Facade - 表面
複雑な処理をまとめてシンプルなAPIを提供するオブジェクトをつくります。
サンプルでは、Editor
、Compiler
、Debugger
の処理をIDE
がintegrate()
でまとめてます。
struct IDE {
let editor = Editor()
let compiler = Compiler()
let debugger = Debugger()
func integrate() {
editor.produceSourceCode()
compiler.compile()
debugger.debug()
}
}
struct Editor {
func produceSourceCode() {
print("ソースコードをつくる")
}
}
struct Compiler {
func compile() {
print("コンパイルする")
}
}
struct Debugger {
func debug() {
print("デバッグする")
}
}
let xcode = IDE()
xcode.integrate()
/* ソースコードをつくる
コンパイルする
デバッグする */
Flyweight - フライ級
インスタンスを効率的に再利用することでメモリの使用量を減らします。
Proxy - 代理人
あるインスタンスが行う処理の中でそのインスタンスが行う必要がないものを代理で行うラッパーをつくります。
以上
Author And Source
この問題について(GoFデザインパターンをSwiftyに書く), 我々は、より多くの情報をここで見つけました https://qiita.com/mlmlykt/items/ebdad4827d0b797e31cf著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .