15
14

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 5 years have passed since last update.

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

Last updated at Posted at 2018-05-27

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

生成に関するパターン

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
15
14
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
15
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?