【Gang of Four】デザインパターン学習 - Proxy


Proxy - 代理

目次
動機の一つとして、生成と初期化にコストのかかるオブジェクトを使用する時(GUIアプリでいう画面に表示する時)に初めてインスタンス化する、といった場合に本パターンを適用します。

ある画面内には1000枚画像を表示する必要がありますが、有効描画領域には10枚までしか表示することができません。にも関わらず初期表示時にGraphicsクラスのオブジェクトを1000個分インスタンス化し、画像を1000枚ロードしていては、初期表示にあまり関係のない処理にリソースを大きく割かなければならなくなります。

そういった課題を解消するために本パターンを用いるようです。本来Graphicsオブジェクトを割り当てておく領域にProxyを割り当てて置き、表示するタイミングになって初めて画像をロードします。

目的

あるオブジェクトへのアクセスを制御するために、そのオブジェクトの代理、または入れ物を提供する。

構成要素

・Proxy 影武者クラス
・Subject ProxyクラスとRealSubjectクラスの抽象クラス
・RealSubject 本物クラス

実装

ではサンプルコードとして画像描画プログラムを実装します。実際に1000個分インスタンス化するのもめんどくさいので初期表示時の描画数を3とします。

Subject ProxyクラスとRealSubjectクラスの抽象クラス

Subject.kt
package proxy

interface Subject {
    fun draw()
}

RealSubject 本物クラス

イメージクラス
インスタンス化した瞬間画像を読み込むクラス

Image.kt

class Image(private val filePath: String): Subject {

    init {
        loadImage(filePath)
    }

    override fun draw() {
        println("${filePath}を描画")
    }

    private fun loadImage(filePath: String) {
        println("${filePath}を読み込み")
    }
}

ではこのImageクラスを使用して画面を初期表示してみます。

クライアントクラス

Client.kt
package proxy

class Client {
    private val initDrawNum = 3

    init {
        val imageList = getNonProxyImageList()
        for (i in 0 until initDrawNum) {
            imageList[i].draw()
        }
    }

    private fun getNonProxyImageList(): ArrayList<Subject> {
        val imageList = ArrayList<Subject>()
        imageList.add(Image("./image/りんご.png"))
        imageList.add(Image("./image/みかん.png"))
        imageList.add(Image("./image/もも.png"))
        imageList.add(Image("./image/ばなな.png"))
        imageList.add(Image("./image/ぱいなっぷる.png"))
        imageList.add(Image("./image/いちご.png"))

        return imageList
    }
}
[out-put]
./image/りんご.pngを読み込み
./image/みかん.pngを読み込み
./image/もも.pngを読み込み
./image/ばなな.pngを読み込み
./image/ぱいなっぷる.pngを読み込み
./image/いちご.pngを読み込み
./image/りんご.pngを描画
./image/みかん.pngを描画
./image/もも.pngを描画

3つ表示するだけで良いものの全ての画像を読み込んでしまっています。Proxyクラスを利用してみます。

Proxy 影武者クラス

イメージProxyクラス
一度読み込んだものは永久に保持するようになっていますが、有効描画領域外になった場合、ロードした画像を解放するメソッドを実装したほうがより良いですね。

しかし、めんどくさいので今回は省略します。

ImageProxy.kt
package proxy

class ImageProxy(private val filePath: String): Subject {
    var image: Image? = null

    override fun draw() {
        image?.let { unwrapImage ->
            unwrapImage.draw()
        } ?: run {
            val tmpImage = Image(filePath)
            tmpImage.draw()
            image = tmpImage
        }
    }
}

再びクライアントクラス。今回はProxyクラスを利用します。

Client.kt
package proxy

class Client {
    private val initDrawNum = 3

    init {
        val imageList = getProxyImageList()
        for (i in 0 until initDrawNum) {
            imageList[i].draw()
        }
    }

    private fun getProxyImageList(): ArrayList<Subject> {
        val proxyImageList = ArrayList<Subject>()
        proxyImageList.add(ImageProxy("./image/りんご.png"))
        proxyImageList.add(ImageProxy("./image/みかん.png"))
        proxyImageList.add(ImageProxy("./image/もも.png"))
        proxyImageList.add(ImageProxy("./image/ばなな.png"))
        proxyImageList.add(ImageProxy("./image/ぱいなっぷる.png"))
        proxyImageList.add(ImageProxy("./image/いちご.png"))

        return proxyImageList
    }

    private fun getNonProxyImageList(): ArrayList<Subject> {
        val imageList = ArrayList<Subject>()
        imageList.add(Image("./image/りんご.png"))
        imageList.add(Image("./image/みかん.png"))
        imageList.add(Image("./image/もも.png"))
        imageList.add(Image("./image/ばなな.png"))
        imageList.add(Image("./image/ぱいなっぷる.png"))
        imageList.add(Image("./image/いちご.png"))

        return imageList
    }
}

出力結果

[out-put]
./image/りんご.pngを読み込み
./image/りんご.pngを描画
./image/みかん.pngを読み込み
./image/みかん.pngを描画
./image/もも.pngを読み込み
./image/もも.pngを描画

今度は表示する分だけロードできるようになりました。