Flyweight

デザインパターンとは

デザインパターンとはオブジェクト思考開発における先人たちが作り上げてきた便利な設計図です。

Gang of Four通称Gofが1994年に出版した『オブジェクト指向における再利用のためのデザインパターン』の中で23個の設計図が紹介されています。

Note

デザインパターンのサンプルコードはSwift4でまとめます。

フライウェイトパターンとは?

フライウェイトパターンはシングルトンパターンに似ているパターンで、一度生成したインスタンスと同じものを作ろうとした時、以前に生成したインスタンスを使い回すパターンです。

インスタンスを管理するクラスで辞書にキーとバリューで保存して同じインスタンスなのかを判断します。

サンプルコード

サンプルコードでは手で星を作るあれ!

名前がわからないですが、画像のようなものを作る際、5人に満たず一人が2本腕を出す可能性があります。

プログラムで再現しようとすると同じインスタンスが出来てしまい無駄になるのでそれを、Flyweightパターンを使って解決します

手のクラス

手のクラスは通常のクラスと変わらず普通に実装して問題ありません。

class Hand {
    var who: String
    init(who hand:String) {
        who = hand
    }
    func output(){
        print("\(who)の手です")
    }
}

Flyweightのロジックを持ったクラス

インスタンス化した手を辞書型として保持しています。インスタンスを作成するときはhandメソッドを使います。

class Factory {
    static var hands: Dictionary = [String: Hand]()
    
    static func hand(str: String) -> Hand {
        var hand = hands[str]
        if hand == nil {
            hand = Hand(who: str)
            hands[str] = hand
        }
    return hand!
    }
}

呼び出し

let harumi1 = Factory.hand(str: "はるみ").output()
let harumi2 = Factory.hand(str: "はるみ").output()
let annna1 = Factory.hand(str: "あんな").output()
let annna2 = Factory.hand(str: "あんな").output()
let michael = Factory.hand(str: "マイケル").output()

print(Factory.hands.count)

staticメソッドで定義しているので直接呼び出すことができます。コンソールには誰の手なのかが出力されます。

最後のprintで出力される数値が3なので重複しているはるみあんなのインスタンスはFlyweightパターンで軽量化できているということがわかります。

このコードにはまだ穴がある!

このコードで一見完成したかのように見えますが、このままでは書きのような非同期で処理が飛んできた際に同じインスタンスを2つ生成してしまいます。

DispatchQueue.global().async {
    let harumi1 = Factory.hand(str: "はるみ").output()
}
DispatchQueue.global().async {
    let harumi1 = Factory.hand(str: "はるみ").output()
}
DispatchQueue.global().async {
    sleep(1)
    print(Factory.hands.count)
}

Flyweightパターンを解説している記事はいくつかあったのですが、私が確認した全てのSwift記事で非同期処理の対応を行なっていませんでした。あまり発生しませんが現状のコードでは同じインスタンスが複数できてしまうい気持ち悪いので修正していきます。

非同期にインスタンスが生成される場合も1つにする!

インスタンスの生成を非同期で行い、処理が終わるのをDispatchSemaphoreで処理待ってからreturnすることで同期処理が実現でき、複数インスタンスを生成しなくて済みます。

 static func hand(str: String) -> Hand {
        var hand = hands[str]
        
        let semaphore = DispatchSemaphore(value: 0)
        DispatchQueue.global().async {
            if hand == nil {
                hand = Hand(who: str)
                hands[str] = hand
            }
            semaphore.signal()
        }
        semaphore.wait()
        return hand!
    }

ソースコード一覧

import UIKit

class Factory {
    static var hands: Dictionary = [String: Hand]()
    
    static func hand(str: String) -> Hand {
        var hand = hands[str]
        
        let semaphore = DispatchSemaphore(value: 0)
        DispatchQueue.global().async {
            if hand == nil {
                hand = Hand(who: str)
                hands[str] = hand
            }
            semaphore.signal()
        }
        semaphore.wait()
        return hand!
    }
}

class Hand {
    var who: String
    init(who hand:String) {
        who = hand
    }
    func output(){
        print("\(who)の手です")
    }
}
DispatchQueue.global().async {
    let harumi1 = Factory.hand(str: "はるみ").output()
}
DispatchQueue.global().async {
    let harumi1 = Factory.hand(str: "はるみ").output()
}
let annna1 = Factory.hand(str: "あんな").output()
let annna2 = Factory.hand(str: "あんな").output()
let michael = Factory.hand(str: "マイケル").output()

DispatchQueue.global().async {
    sleep(1)
    print(Factory.hands.count)
}

参考文献

Flyweight パターン

IT 専科 Flyweight パターン

Swiftで学ぶデザインパターン20 (Flyweightパターン)

Swiftで非同期処理を同期処理にする

[Swift]Dictionary<Key, Value>型

デザインパターン

前の記事

Singleton
デザインパターン

次の記事

Facade