Edited at

「Swiftデザインパターン」に出てきたパターンまとめ

More than 1 year has passed since last update.

「Swiftデザインパターン」をパラパラと読んだので、せっかくなので出てきたパターンをまとめてみた。基本的に概要と実装しか書いていないので、細かいところは書籍を参照していただければ。

https://www.amazon.co.jp/Swift-Programmers-SELECTION/dp/4798142492


生成に関するパターン


Prototypeパターン

プロトタイプ と呼ばれる既存のオブジェクトをコピーすることで、新しいオブジェクトを作成するデザインパターンである。これにより、オブジェクトを使用するコンポーネントからオブジェクト作成するコードを隠蔽できる。

Classだと参照渡しになるが、NSCopyingプロトコルに準拠させてcopy()を呼ぶとディープコピーが可能になる。

class Appointment: NSObject, NSCopying {

var name: String
var day: String
var place: String

func printDetails(label: String) {
print("\(label) with \(name) on \(day) at \(place)")
}

func copyWithZone(zone: NSZone) -> AnyObject {
return Appointment(name: self.name, day: self.day, place: self.place)
}
}

var beerMeeting = Appointment(name: "Bob", day: "Mon", place: "Joe's Bar")

var workMeeting = beerMeeting.copy() as! Appointment
workMeeting.name = "Alice"
workMeeting.day = "Fri"
workMeeting.place = "Conference Rm 2"

beerMeeting.printDetails(label: "Social") // Social with Bob on Mon at Joe's Bar
workMeeting.printDetails(label: "Work") // Work with Alice on Fri at Conference Rm 2


Singletonパターン

アプリケーションにおいて特定の型のオブジェクトを1つだけ存在させるデザインパターンである。オブジェクトを作成しても利用可能な現実のリソースが増えない、またはロギングといったアクティビティを統一したい場合に使用される。

class Logger {

private var data = [String]()

private init() {

}

private func doLog(msg: String) {
data.append(msg)
}

private func doPrintLog() {
for msg in data {
print("Log: \(msg)")
}
}

func log(msg: String) {
Logger.sharedInstance.log(msg: msg)
}

func printLog() {
Logger.sharedInstance.printLog()
}

private class var sharedInstance: Logger {
get {
struct SingletonWrapper {
static let singleton = Logger()
}
return SingletonWrapper.singleton
}
}
}


Object Poolパターン

単一のインスタンスではなく、複数の同じ型のオブジェクトへのアクセスを可能にするデザインパターンである。まったく同じオブジェクトがいくつかあり、新しいインスタンスの生成にコストがかかるために、それらの作成を管理しなければならない場合に使用される。CocoaフレームワークのUITableViewCellの再利用などが代表例である。

class Pool<T> {

private var data = [T]()

init(items: [T]) {
data.reserveCapacity(items.count)
data.append(contentsOf: items)
}

func getFromPool() -> T? {
guard !data.isEmpty else {
return nil
}
return self.data.remove(at: 0)
}
}


Factory Methodパターン

共通のプロトコルを実装する、または同じベースクラスを共有するクラスの中からどれかを選択できる場合に使用されるデザインパターンである。

class RentalCar {

private var nameBV: String
private var passengersBV: Int
private var priceBV: Float

init(name: String, passengers: Int, price: Float) {
self.nameBV = name
self.passengersBV = passengers
self.priceBV = price
}

final var name: String {
return nameBV
}

final var passengers: Int {
return passengersBV
}

final var price: Float {
return priceBV
}

class func createRentalCar(passengers: Int) -> RentalCar? {
switch passengers {
case 0...3:
return Compact()
case 4...8:
return SUV()
default:
return nil
}
}
}

class Compact: RentalCar {
init() {
super.init(name: "VM Golf", passengers: 3, price: 20)
}
}

class SUV: RentalCar {
init() {
super.init(name: "Cadillac Escalade", passengers: 8, price: 75)
}
}


Abstract Factoryパターン

Factory Methodパターンに似ているが、呼び出し元のコンポーネントが関連するオブジェクトのファミリまたはグループを取得できるという違いがある。

class CarFactory {

func createFloorplan() -> Floorplan {
fatalError("Not implemented")
}

func createSuspension() -> Suspension {
fatalError("Not implemented")
}

func createDrivetrain() -> Drivetrain {
fatalError("Not implemented")
}

final class func getFactory(cars: Cars) -> CarFactory? {
switch cars {
case .compact:
return CompactCarFactory()
case .suv:
return SUVCarFactory()
}
}
}

class CompactCarFactory: CarFactory {
override func createFloorplan() -> Floorplan {
return StandardFloorplan()
}

override func createSuspension() -> Suspension {
return RoadSuspension()
}

override func createDrivetrain() -> Drivetrain {
return FrontWheelDrive()
}
}

class SUVCarFactory: CarFactory {
override func createFloorplan() -> Floorplan {
return LongFloorplan()
}

override func createSuspension() -> Suspension {
return OffRoadSuspension()
}

override func createDrivetrain() -> Drivetrain {
return AllWheelDrive()
}
}

let factory = CarFactory.getFactory(cars: Cars.compact)

if let factory = factory {
let car = Car(carType: Cards.compact, floor: factory.createFloorplan(), suspension: factory.createSuspension(), drive: factory.createDrivetrain())
}


Builderパターン

オブジェクトの設定をその作成から切り離すために使用されるデザインパターンである。

class Burger {

let customerName: String
let veggieProduct: Bool
let patties: Int
let pickles: Bool
let mayo: Bool
var ketchup: Bool
let lettuce: Bool
let cook: Cooked

enum Cooked: String {
case rare = "Rare"
case normal = "Normal"
case welldone = "Well Done"
}

init(name: String, veggie: Bool, patties: Int, pickles: Bool, mayo: Bool, ketchup: Bool, lettuce: Bool, cook: Cooked) {
self.customerName = name
self.veggieProduct = veggie
self.patties = patties
self.pickles = pickles
self.mayo = mayo
self.ketchup = ketchup
self.lettuce = lettuce
self.cook = cook
}
}

class BurgerBuilder {
private var veggie = false
private var pickles = true
private var mayo = true
private var ketchup = true
private var lettuce = true
private var cooked = Burger.Cooked.normal
private var patties = 2

func setVeggie(choice: Bool) { self.veggie = choice }
func setPickles(choice: Bool) { self.pickles = choice }
func setMayo(choice: Bool) { self.mayo = choice }
func setKetchup(choice: Bool) { self.ketchup = choice }
func setLettuce(choice: Bool) { self.lettuce = choice }
func setCooked(choice: Burger.Cooked) { self.cooked = choice }
func setPatties(choice: Int) { self.patties = choice }

func buildObject(name: String) -> Burger {
return Burger(name: name, veggie: veggie, patties: patties, pickles: pickles, mayo: mayo, ketchup: ketchup, lettuce: lettuce, cook: cooked)
}
}


構造に関するパターン


Adapterパターン

関連する機能を提供する2つのオブジェクトをーそれらのAPIに互換性がなかったとしてもー組み合わせることができる。

SalesDataSource, NewCoStaffMemberはそれぞれに互換性がないが、NewCoStaffMemberにEmployeeDataSourceを適用したExtensionを生やして組み合わせることを可能にしている。またラッパークラスを用意することでも組み合わせることを可能にできる。

struct Employee {

var name: String
var title: String
}

protocol EmployeeDataSource {
var employees: [Employee] { get }
func searchByName(name: String) -> [Employee]
func searchByTitle(title: String) -> [Employee]
}

class DataSourceBase: EmployeeDataSource {
var employees = [Employee]()

func searchByName(name: String) -> [Employee] {
return search(selector: { e -> Bool in
return e.title.range(of: name) != nil
})
}

func searchByTitle(title: String) -> [Employee] {
return search(selector: { e -> Bool in
return e.title.range(of: title) != nil
})
}

private func search(selector: (Employee) -> Bool) -> [Employee] {
var results = [Employee]()
for e in employees {
if selector(e) {
results.append(e)
}
}
return results
}
}

class SalesDataSource: DataSourceBase {
override init() {
super.init()
employees.append(Employee(name: "Alice", title: "VP of Sales"))
employees.append(Employee(name: "Bob", title: "Account Exec"))
}
}

class NewCoStaffMember {
private var name: String
private var role: String

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

func getName() -> String {
return name
}

func getJob() -> String {
return role
}
}

class NewCoDirectory {
private var staff: [String: NewCoStaffMember]

init() {
staff = [
"Hans": NewCoStaffMember(name: "Hans", role: "Corp Counsel"),
"Greta": NewCoStaffMember(name: "Greta", role: "VP, Legal")
]
}

func getStaff() -> [String: NewCoStaffMember] {
return staff
}
}

extension NewCoDirectory: EmployeeDataSource {
var employees: [Employee] {
return getStaff().values.map { sv -> Employee in
return Employee(name: sv.getName(), title: sv.getJob())
}
}

func searchByName(name: String) -> [Employee] {
return createEmployees() {
return $0.getName().range(of: name) != nil
}
}

func searchByTitle(title: String) -> [Employee] {
return createEmployees() {
return $0.getJob().range(of: title) != nil
}
}

private func createEmployees(filter filterClosure: @escaping ((NewCoStaffMember) -> Bool)) -> [Employee] {
return getStaff().values
.filter { filterClosure($0) }
.map { Employee(name: $0.getName(), title: $0.getJob())}
}
}


Bridgeパターン

Adapterパターンと同じように見えるが、Adapterパターンとの最大の相違点は意図であり、実装ではない。

主に「クラス階層の爆発」などの問題を解決する。

protocol ClearMessageChannel {

func send(message: String)
}

protocol SecureMessageChannel {
func sendEncryptedMessage(encryptedText: String)
}

class Communicator {
private let clearChannel: ClearMessageChannel
private let secureChannel: SecureMessageChannel

init(clearChannel: ClearMessageChannel,
secureChannel: SecureMessageChannel) {
self.clearChannel = clearChannel
self.secureChannel = secureChannel
}

func sendCleartextMessage(message: String) {
self.clearChannel.send(message: message)
}

func sendSecureMessage(message: String) {
self.secureChannel.sendEncryptedMessage(encryptedText: message)
}
}

protocol Message {
init(message: String)
func prepareMessage()
var contentToSend: String { get }
}

class ClearMessage: Message {
private var message: String

required init(message: String) {
self.message = message
}
func prepareMessage() {
// do nothing.
}

var contentToSend: String {
return message
}
}

class EncryptedMessage: Message {
private var clearText: String
private var cipherText: String?

required init(message: String) {
self.clearText = message
}

func prepareMessage() {
cipherText = String(clearText.characters.reversed())
}

var contentToSend: String {
return cipherText!
}
}

protocol Channel {
func sendMessage(msg: Message)
}

class LandlineChannel: Channel {
func sendMessage(msg: Message) {
print("Landline: \(msg.contentToSend)")
}
}

class WirelessChannel: Channel {
func sendMessage(msg: Message) {
print("Wireless: \(msg.contentToSend)")
}
}

class CommunicatorBridge: ClearMessageChannel, SecureMessageChannel {
private var channel: Channel

init(channel: Channel) {
self.channel = channel
}

func send(message: String) {
let msg = ClearMessage(message: message)
sendMessage(msg: msg)
}

func sendEncryptedMessage(encryptedText: String) {
let msg = EncryptedMessage(message: encryptedText)
sendMessage(msg: msg)
}

private func sendMessage(msg: Message) {
msg.prepareMessage()
channel.sendMessage(msg: msg)
}
}

var bridge = CommunicatorBridge(channel: LandlineChannel())
var comms = Communicator(clearChannel: bridge, secureChannel: bridge)

comms.sendCleartextMessage(message: "Hello!")
comms.sendSecureMessage(message: "This is a secret")


Decoratorパターン

オブジェクトを作成するために使用されるクラスを変更せずに、個々のサブジェクトの振る舞いを変更できるようにするパターンである。

通常は、クラスを直接変更するほうが簡単なのだが、サードパーティーのライブラリなどの理由で変更が困難な場合に有効である。

class Purchase {

private let product: String
private let price: Float

init(product: String, price: Float) {
self.product = product
self.price = price
}

var description: String {
return product
}

var totalPrice: Float {
return price
}
}

class BasePruchaseDecorator: Purchase {
init(purchase: Purchase) {
super.init(product: purchase.description, price: purchase.totalPrice)
}
}

class PurchaseWithGiftWrap: BasePruchaseDecorator {
override var description: String {
return "\(super.description) + giftwrap"
}
override var totalPrice: Float {
return super.totalPrice + 2
}
}

class PurchaseWithRibbon: BasePruchaseDecorator {
override var description: String {
return "\(super.description) + ribbon"
}
override var totalPrice: Float {
return super.totalPrice + 1
}
}

let purchase = PurchaseWithRibbon(purchase:
PurchaseWithGiftWrap(purchase:
Purchase(product: "Sunglasses", price: 25)))

print(purchase.description) // Sunglasses + giftwrap + ribbon
print(purchase.totalPrice) // 28.0


Compositeパターン

個々のオブジェクトとオブジェクトのコレクションからなるツリーを一貫した方法で扱えるようにするパターンである。

protocol CarPart {

var name: String { get }
var price: Float { get }
}

class Part: CarPart {
let name: String
let price: Float

init(name: String, price: Float) {
self.name = name
self.price = price
}
}

class CompositePart: CarPart {
let name: String
let parts: [CarPart]

init(name: String, parts: [CarPart]) {
self.name = name
self.parts = parts
}

var price: Float {
return parts.reduce(0) { $0 + $1.price }
}
}


Facadeパターン

共通のタスクを実行するための複雑なAPIの使用を単純にするパターンである。

class TreasureMap {

enum Treasures {
case galleon
case buried_gold
case sunken_jewels
}

struct MapLocation {
let gridLetter: Character
let gridNumber: UInt
}

func findTreasure(type: Treasures) -> MapLocation {
switch type {
case .galleon:
return MapLocation(gridLetter: "D", gridNumber: 6)
case .buried_gold:
return MapLocation(gridLetter: "C", gridNumber: 2)
case .sunken_jewels:
return MapLocation(gridLetter: "F", gridNumber: 12)
}
}
}

class PirateShip {
struct ShipLocation {
let NorthSourh: Int
let EastWest: Int
}

var currentPosition: ShipLocation
var movementQueue = DispatchQueue(label: "shipQ")

init() {
currentPosition = ShipLocation(NorthSourh: 5, EastWest: 5)
}

func moveToLocation(location: ShipLocation, callback: @escaping (ShipLocation) -> Void) {
movementQueue.async {
self.currentPosition = location
callback(self.currentPosition)
}
}
}

class PirateCrew {
let workQueue = DispatchQueue(label: "crewWorkQ")

enum Actions {
case attackShip
case digForGold
case diveForJewels
}

func performAction(action: Actions, callback: @escaping (Int) -> Void) {
workQueue.async {
var prizeValue = 0
switch action {
case .attackShip:
prizeValue = 10000
case .digForGold:
prizeValue = 5000
case .diveForJewels:
prizeValue = 1000
}
callback(prizeValue)
}
}
}

enum TreasureTypes {
case ship
case buried
case sunken
}

class PirateFacade {
private let map = TreasureMap()
private let ship = PirateShip()
private let crew = PirateCrew()

func getTreasure(type: TreasureTypes) -> Int? {
var prizeAmount: Int?

var treasureMapType: TreasureMap.Treasures
var crewWorkType: PirateCrew.Actions

switch type {
case .ship:
treasureMapType = .galleon
crewWorkType = .attackShip
case .buried:
treasureMapType = .buried_gold
crewWorkType = .digForGold
case .sunken:
treasureMapType = .sunken_jewels
crewWorkType = .diveForJewels
}

let treasureLocation = map.findTreasure(type: treasureMapType)

let sequence: [Character] = ["A", "B", "C", "D", "E", "F", "G"]
let eastWestPos = sequence.enumerated().filter({ $0.1 == treasureLocation.gridLetter}).map { $0.0 }.first!
let shipTarget = PirateShip.ShipLocation(NorthSourh: Int(treasureLocation.gridNumber), EastWest: eastWestPos)

let semaphore = DispatchSemaphore(value: 0)

ship.moveToLocation(location: shipTarget) { location in
self.crew.performAction(action: crewWorkType, callback: { prize in
prizeAmount = prize
semaphore.signal()
})
}

semaphore.wait(timeout: .distantFuture)
return prizeAmount
}
}


Flyweightパターン

複数の呼び出し元のコンポーネントに同じデータオブジェクトを共有させるパターンである。

class Owner: NSObject, NSCopying {

var name: String
var city: String

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

func copy(with zone: NSZone? = nil) -> Any {
return Owner(name: self.name, city: self.city)
}
}

class FlyweightFactory {
class func createFlyweight() -> Flyweigt {
return Flyweigt(owner: ownerSingleton)
}

private class var ownerSingleton: Owner {
get {
struct SingletonWrapper {
static let singleton = Owner(name: "Anonymous", city: "Anywhere")
}
return SingletonWrapper.singleton
}
}
}

class Flyweigt {
private let extrinsicOwner: Owner
private var intrinsicOwner: Owner?

init(owner: Owner) {
self.extrinsicOwner = owner
}

var name: String {
get {
return intrinsicOwner?.name ?? extrinsicOwner.name
}
set (value) {
decoupleFromExtrinsic()
intrinsicOwner?.name = value
}
}

var city: String {
get {
return intrinsicOwner?.city ?? extrinsicOwner.city
}
set (value) {
decoupleFromExtrinsic()
intrinsicOwner?.city = value
}
}

private func decoupleFromExtrinsic() {
if intrinsicOwner == nil {
intrinsicOwner = extrinsicOwner.copy(with: nil) as? Owner
}
}
}


Proxyパターン

オブジェクトを別オブジェクトまたはリソースへのインターフェイスとして機能させる必要がある場合に使用される。

protocol HttpHeaderRequest {

func getHeader(url: String, header: String) -> String?
}

class HttpHeaderRequestProxy: HttpHeaderRequest {
private let semaphore = DispatchSemaphore(value: 0)

func getHeader(url urlString: String, header: String) -> String? {
var headerValue: String?

let url = URL(string: urlString)
let request = URLRequest(url: url!)
URLSession.shared.dataTask(with: request) { (data, response, error) in
if let httpResponse = response as? HTTPURLResponse {
headerValue = httpResponse.allHeaderFields[header] as? String
}
self.semaphore.signal()
}.resume()
semaphore.wait(timeout: .distantFuture)
return headerValue
}
}

let url = "http://www.apress.com"
let headers = ["Content-Length", "Content-Encoding"]

let proxy = HttpHeaderRequestProxy()

for header in headers {
if let val = proxy.getHeader(url: url, header: header) {
print("\(header): \(val)")
}
}

FileHandle.standardInput.availableData


振る舞いに関するパターン


Chain of Responsibilityパターン

呼び出し元のコンポーネントからのリクエストを処理することが可能な複数のオブジェクトを数珠つなぎにするパターンである。

struct Message {

let from: String
let to: String
let subject: String
}

class Transmitter {
var nextLink: Transmitter?

required init() {}

func sendMessage(message: Message) {
guard let nextLink = nextLink else {
print("End of chain reached. Message not send.")
return
}
nextLink.sendMessage(message: message)
}

class func createChain() -> Transmitter? {
let transmitterClasses: [Transmitter.Type] = [
PriorityTransmitter.self,
LocalTransmitter.self,
RemoteTransmitter.self
]

var link: Transmitter?

for tClass in transmitterClasses.reversed() {
let existingLink = link
link = tClass.init()
link?.nextLink = existingLink
}

return link
}

fileprivate class func matchEmailSuffix(message: Message) -> Bool {
guard let index = message.from.range(of: "@") else {
return false
}
return message.to.hasSuffix(message.from.substring(with: Range(uncheckedBounds: (lower: index.lowerBound, upper: message.from.endIndex))))
}
}

class LocalTransmitter: Transmitter {
override func sendMessage(message: Message) {
if Transmitter.matchEmailSuffix(message: message) {
print("Message to \(message.to) sent locally")
} else {
super.sendMessage(message: message)
}
}
}

class RemoteTransmitter: Transmitter {
override func sendMessage(message: Message) {
if !Transmitter.matchEmailSuffix(message: message) {
print("Message to \(message.to) sent remotely")
} else {
super.sendMessage(message: message)
}
}
}

class PriorityTransmitter: Transmitter {
override func sendMessage(message: Message) {
if message.subject.hasPrefix("Priority") {
print("Message to \(message.to) sent as priority")
} else {
super.sendMessage(message: message)
}
}
}

let messages = [
Message(from: "bob@example.com", to: "joe@example.com", subject: "Free for lunch?"),
Message(from: "joe@example.com", to: "alice@acme.com", subject: "Nex Contracts"),
Message(from: "pete@example.com", to: "all@example.com", subject: "Priority: All-Hands Meeting")
]

if let chain = Transmitter.createChain() {
for msg in messages {
chain.sendMessage(message: msg)
}
}

/*
Message to joe@example.com sent locally
Message to alice@acme.com sent remotely
Message to all@example.com sent as priority
*/


Commandパターン

オブジェクトのメソッドを呼び出す方法をカプセル化し、そのメソッドを別のタイミングで呼び出せるようにするか、異なるコンポーネントで呼び出せるようにするために使用されるパターンである。

class Calculator {

private(set) var total = 0
private var hisotory = [Command]()

func add(_ amount: Int) {
addUndoCommand(method: Calculator.subtract, amount: amount)
total += amount
}

func subtract(_ amount: Int) {
addUndoCommand(method: Calculator.add, amount: amount)
total -= amount
}

func multiply(_ amount: Int) {
addUndoCommand(method: Calculator.divide, amount: amount)
total *= amount
}

func divide(_ amount: Int) {
addUndoCommand(method: Calculator.multiply, amount: amount)
total /= amount
}

private func addUndoCommand(method: @escaping (Calculator) -> (Int) -> Void, amount: Int) {
hisotory.append(GenericsCommand.createCommand(receiver: self, instructions: { calc in
method(calc)(amount)
}))
}

func undo() {
guard !hisotory.isEmpty else {
return
}

hisotory.removeLast().execute()
hisotory.removeLast()
}
}

protocol Command {
func execute()
}

class GenericsCommand<T>: Command {
private var receiver: T
private var instructions: (T) -> Void

init(receiver: T, instructions: @escaping (T) -> Void) {
self.receiver = receiver
self.instructions = instructions
}

func execute() {
instructions(receiver)
}

class func createCommand(receiver: T, instructions: @escaping (T) -> Void) -> Command {
return GenericsCommand(receiver: receiver, instructions: instructions)
}
}

let calc = Calculator()
calc.add(10)
calc.multiply(4)
calc.subtract(2)

print("Total: \(calc.total)") // Total: 38

for _ in 0..<3 {
calc.undo()
print("Undo called. Total: \(calc.total)")
}

// Undo called: Total: 40
// Undo called: Total: 10
// Undo called: Total: 0


Mediatorパターン

オブジェクトのグループ同士のやり取りを単純かつ合理的なものにするために使用される。

struct Position {

var distanceFromRunway: Int
var height: Int
}

protocol Peer {
var name: String { get }
func otherPlaneDidChangePosition(position: Position) -> Bool
}

protocol Mediator {
func registerPeer(peer: Peer)
func unregisterPeer(peer: Peer)
func changePosition(peer: Peer, pos: Position) -> Bool
}

class AirplaneMediator: Mediator {
private var peers: [String: Peer] = [:]

func registerPeer(peer: Peer) {
peers[peer.name] = peer
}

func unregisterPeer(peer: Peer) {
peers.removeValue(forKey: peer.name)
}

func changePosition(peer: Peer, pos: Position) -> Bool {
for storedPeer in peers.values {
if peer.name != storedPeer.name && storedPeer.otherPlaneDidChangePosition(position: pos) {
return true
}
}
return false
}
}

class Airplane: Peer {
var name: String
var currentPosition: Position
var mediator: Mediator

init(name: String, initiailPos: Position, mediator: Mediator) {
self.name = name
self.currentPosition = initiailPos
self.mediator = mediator
mediator.registerPeer(peer: self)
}

func otherPlaneDidChangePosition(position: Position) -> Bool {
return position.distanceFromRunway == self.currentPosition.distanceFromRunway &&
abs(position.height - self.currentPosition.height) < 1000
}

func changePosition(newPosition: Position) {
currentPosition = newPosition
guard !mediator.changePosition(peer: self, pos: self.currentPosition) else {
print("\(name): Too close! Abort!")
return
}
print("\(name): Position changed")
}

func land() {
currentPosition = Position(distanceFromRunway: 0, height: 0)
mediator.unregisterPeer(peer: self)
print("\(name): Landed")
}
}

let mediator = AirplaneMediator()

let british = Airplane(name: "BA706", initiailPos: Position(distanceFromRunway: 11, height: 21000), mediator: mediator)
let american = Airplane(name: "AA101", initiailPos: Position(distanceFromRunway: 12, height: 22000), mediator: mediator)

british.changePosition(newPosition: Position(distanceFromRunway: 8, height: 10000))
british.changePosition(newPosition: Position(distanceFromRunway: 2, height: 5000))
british.changePosition(newPosition: Position(distanceFromRunway: 1, height: 1000))

let cathay = Airplane(name: "CX200", initiailPos: Position(distanceFromRunway: 13, height: 22000), mediator: mediator)

british.land()

cathay.changePosition(newPosition: Position(distanceFromRunway: 12, height: 22000))

// BA706: Position changed
// BA706: Position changed
// BA706: Position changed
// BA706: Landed
// CX200: Too close! Abort!


Observerパターン

Observerパターンは、オブジェクトでの変化に関する通知の受け取りを別のオブジェクトが登録できるようにするパターン

protocol Observer: class {

func notifiy(user: String, success: Bool)
}

protocol Subject {
func addObservers(observers: [Observer])
func removeObserver(observer: Observer)
}

class SubjectBase: Subject {
private var observers = [Observer]()
private var collectionQueue = DispatchQueue(label: "colQ", attributes: .concurrent)

func addObservers(observers: [Observer]) {
collectionQueue.async {
for newOb in observers {
self.observers.append(newOb)
}
}
}

func removeObserver(observer: Observer) {
collectionQueue.async {
self.observers = self.observers.filter({ $0 !== observer })
}
}

func sendNotification(user: String, success: Bool) {
collectionQueue.sync {
for ob in self.observers {
ob.notifiy(user: user, success: success)
}
}
}
}

class AuthenticationManager: SubjectBase {
func authenticate(user: String, pass: String) -> Bool {
var result = false
if user == "bob" && pass == "secret" {
result = true
print("User \(user) is authenticated")
} else {
print("Failed authentication attempt")
}
sendNotification(user: user, success: result)
return result
}
}

class ActivityLog: Observer {
func notifiy(user: String, success: Bool) {
print("Auth request for \(user). Success: \(success)")
}

func logActivity(activity: String) {
print("Log: \(activity)")
}
}

class FileCache: Observer {
func notifiy(user: String, success: Bool) {
if success {
loadFiles(user: user)
}
}

func loadFiles(user: String) {
print("Load files for \(user)")
}
}

class AttackMonitor: Observer {
func notifiy(user: String, success: Bool) {
monitorSubpiciousActivity = !success
}

var monitorSubpiciousActivity: Bool = false {
didSet {
print("Monitoring for attack: \(monitorSubpiciousActivity)")
}
}
}

let monitor = AttackMonitor()
let log = ActivityLog()
let cache = FileCache()

let authManager = AuthenticationManager()
authManager.addObservers(observers: [log, cache, monitor])

authManager.authenticate(user: "bob", pass: "secret")
print("-----")
authManager.authenticate(user: "joe", pass: "shhh")

// User bob is authenticated
// Auth request for bob. Success: true
// Load files for bob
// Monitoring for attack: false
// -----
// Failed authentication attempt
// Auth request for joe. Success: false
// Monitoring for attack: true


Mementoパターン

オブジェクトの完全な状態をメメントに取り込み、あとからオブジェクトをリセットをするために使用できるようにするパターンである。

protocol Memento {}

protocol Originator {
func createMemento() -> Memento
func applyMemento(memento: Memento)
}

class LedgerEntry {
let id: Int
let counterParty: String
let amount: Float

init(id: Int, counterParty: String, amount: Float) {
self.id = id
self.counterParty = counterParty
self.amount = amount
}
}

class LedgerMemento: Memento {
private var entries: [LedgerEntry] = []
private let total: Float
private let nextId: Int

init(ledger: Ledger) {
self.entries = ledger.entries.values.map { $0 }
self.total = ledger.total
self.nextId = ledger.nextId
}

func apply(ledger: Ledger) {
ledger.total = self.total
ledger.nextId = self.nextId
ledger.entries.removeAll(keepingCapacity: true)
for entry in self.entries {
ledger.entries[entry.id] = entry
}
}
}

class Ledger: Originator {
fileprivate var entries: [Int: LedgerEntry] = [:]
fileprivate var nextId = 1
var total: Float = 0

func addEntry(counterParty: String, amount: Float) {
let entry = LedgerEntry(id: nextId, counterParty: counterParty, amount: amount)
nextId += 1
entries[entry.id] = entry
total += amount
}

func createMemento() -> Memento {
return LedgerMemento(ledger: self)
}

func applyMemento(memento: Memento) {
if let m = memento as? LedgerMemento {
m.apply(ledger: self)
}
}

func printEntries() {
for id in entries.keys.sorted(by: { $0 < $1 }) {
if let entry = entries[id] {
print("#\(id): \(entry.counterParty) $\(entry.amount)")
}
}
print("Total: $\(total)")
print("----")
}
}

let ledger = Ledger()

ledger.addEntry(counterParty: "Bob", amount: 100.43)
ledger.addEntry(counterParty: "Joe", amount: 200.20)

let memento = ledger.createMemento()

ledger.addEntry(counterParty: "Alice", amount: 500)
ledger.addEntry(counterParty: "Tony", amount: 20)

ledger.printEntries()

ledger.applyMemento(memento: memento)

ledger.printEntries()

// #1: Bob $100.43
// #2: Joe $200.2
// #3: Alice $500.0
// #4: Tony $20.0
// Total: $820.63
// ----
// #1: Bob $100.43
// #2: Joe $200.2
// Total: $300.63
// ----


Strategyパターン

明確に定義されたプロトコルに準拠するアルゴリズムオブジェクトを利用して、修正せずに拡張することが可能なクラスを作成するために使用されるパターンである。

protocol Strategy {

func execute(values: [Int]) -> Int
}

class SumStrategy: Strategy {
func execute(values: [Int]) -> Int {
return values.reduce(0) { $0 + $1 }
}
}

class MultiplyStrategy: Strategy {
func execute(values: [Int]) -> Int {
return values.reduce(1) { $0 * $1 }
}
}

final class Sequence {
private var numbers: [Int]

init(_ numbers: Int...) {
self.numbers = numbers
}

func addNumber(_ value: Int) {
self.numbers.append(value)
}

func compute(strategy: Strategy) -> Int {
return strategy.execute(values: self.numbers)
}
}

let sequence = Sequence(1, 2, 3, 4)
sequence.addNumber(10)
sequence.addNumber(20)

let sumStrategy = SumStrategy()
let multiplyStrategy = MultiplyStrategy()

print("Sum: \(sequence.compute(strategy: sumStrategy))") // Sum: 40
print("Multiply: \(sequence.compute(strategy: multiplyStrategy))") // Multiply: 4800


Visitorパターン

異種のオブジェクトからなるコレクションを操作するための新しいアルゴリズムを定義できるようにするパターンである。

protocol Shape {

func accept(visitor: Visitor)
}

protocol Visitor {
func visit(shape: Circle)
func visit(shape: Square)
func visit(shape: Rectangle)
}

class AreaVisitor: Visitor {
var totalArea: Float = 0

func visit(shape: Circle) {
totalArea += (3.14 * powf(shape.radius, 2))
}

func visit(shape: Square) {
totalArea += powf(shape.length, 2)
}

func visit(shape: Rectangle) {
totalArea += (shape.xLen * shape.yLen)
}
}

class Circle: Shape {
let radius: Float

init(radius: Float) {
self.radius = radius
}

func accept(visitor: Visitor) {
visitor.visit(shape: self)
}
}

class Square: Shape {
let length: Float

init(length: Float) {
self.length = length
}

func accept(visitor: Visitor) {
visitor.visit(shape: self)
}
}

class Rectangle: Shape {
let xLen: Float
let yLen: Float

init(x: Float, y: Float) {
self.xLen = x
self.yLen = y
}

func accept(visitor: Visitor) {
visitor.visit(shape: self)
}
}

class ShapeCollection {
let shapes: [Shape]

init() {
shapes = [
Circle(radius: 2.5),
Square(length: 4),
Rectangle(x: 10, y: 2)
]
}
func accept(visitor: Visitor) {
for shape in shapes {
shape.accept(visitor: visitor)
}
}
}

let shapes = ShapeCollection()
let areaVisitor = AreaVisitor()
shapes.accept(visitor: areaVisitor)
print("Area: \(areaVisitor.totalArea)") // Area: 55.625


Template Methodパターン

アルゴリズムの特定のステップをサードパーティによって提供される実装に置き換えることができるパターンである。

struct Donor {

let title: String
let firstName: String
let familyName: String
let lastDonation: Float
}

class DonorDatabase {
private var donors: [Donor]
var filter: (([Donor]) -> [Donor])?
var generate: (([Donor]) -> [String])?

init() {
donors = [
Donor(title: "MS", firstName: "Anne", familyName: "Jones", lastDonation: 0),
Donor(title: "Mr", firstName: "Bob", familyName: "Smith", lastDonation: 100),
Donor(title: "Dr", firstName: "Alice", familyName: "Doe", lastDonation: 200),
Donor(title: "Prof", firstName: "Joe", familyName: "Davis", lastDonation: 320)
]
}

func generate(maxNumber: Int) -> [String] {
var targetDonors = filter?(donors) ?? donors.filter { $0.lastDonation > 0 }
targetDonors.sort { $0.0.lastDonation > $0.1.lastDonation }
if targetDonors.count > maxNumber {
targetDonors = Array(targetDonors[0..<maxNumber])
}
return generate?(targetDonors) ?? targetDonors.map { donor in
return "Dear \(donor.title). \(donor.familyName)"
}
}
}

let donorDb = DonorDatabase()

let galaInvitations = donorDb.generate(maxNumber: 2)
for invite in galaInvitations {
print(invite)
}

donorDb.filter = { $0.filter { $0.lastDonation == 0 } }
donorDb.generate = { $0.map { "Hi \($0.firstName)" } }

let newDonors = donorDb.generate(maxNumber: Int.max)
for invite in newDonors {
print(invite)
}

// Dear Prof. Davis
// Dear Dr. Doe
// Hi Anne