はじめに
本エントリーは某社内で実施するデザインパターン勉強会向けの資料となります。
本エントリーで書籍「Java言語で学ぶデザインパターン入門」をベースに学習を進めますが、サンプルコードはC#に置き換えて解説します。
第1回:Iteratorパターン
第2回:Adapterパターン
第3回:Template Methodパターン
第4回:Factory Methodパターン
第5回:Singletonパターン
第6回:Prototypeパターン
第7回:Builderパターン
第8回:Abstract Factoryパターン
第9回:Bridgeパターン
第10回:Strategyパターン
第11回:Compositeパターン
第12回:Decoratorパターン
第13回:Visitorパターン
第14回:Chain of Responsibilityパターン
第15回:Facadeパターン
第16回:Mediatorパターン
第17回:Observerパターン
第18回:Mementoパターン
第19回:Stateパターン
第20回:Flyweightパターン
Proxyパターンとは
Proxyは、「代理人」という意味です。代理人とは仕事を行うべき人の代わりにその仕事を代理で行う人を指します。代理人は本人でなくても出来るような仕事を行い、出来る範囲を超えた仕事がやってきたら本人に仕事を受け渡します。
つまり、頻繁に呼び出される、重い処理があるといったオブジェクトの代わりに代理人オブジェクトがある程度の処理を代理で行うのがProxyパターンです。
Proxyパターンのクラスは以下になります。
名前 | 役割 |
---|---|
Subject(主体) | ProxyとRealSubjectを同一視するためのインタフェース |
Proxy(代理人) | Clientからの要求を処理するクラス 手に負えない処理の場合はRealSubjectを呼び出す |
RealSubject(本人) | Proxyで行えない処理を行う |
Client(依頼人) | Proxyパターンを利用する |
今回のサンプルプログラムは、名前の設定と取得を行い画面上に名前を表示する簡単なプログラムですが本人クラスのインスタンス生成に時間がかかる、といった設定で作成します。名前の設定と取得は代理人クラスが行い、文字列の表示は本人クラスが行います。
クラス図
各クラスの役割
名前 | 役割 |
---|---|
IPrintable | PrinterとPrinterProxyのインタフェース(Subject) |
Printer | 名前付きのプリンタを表すクラス(RealSubject) |
PrinterProxy | 名前付きのプリンタを表すクラス(Proxy) |
Program | 動作テスト用のクラス(Client) |
ソースコード
IPrintableインタフェース
PrinterクラスとPrinterProxyクラスを同一視するためのインタフェースです。SetPrinterNameメソッドは名前の設定、GetPrinterNameメソッドは名前の取得、Printメソッドが文字列を表示するためのメソッドです。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Proxy
{
interface IPrintable
{
void SetPrinterName(string name); //名前の設定
string GetPrinterName(); //名前の取得
void Print(string str); //文字列表示
}
}
Printerクラス
本人を表すクラスです。インスタンス生成に時間がかかるという設定なのでコンストラクタでは、HeavyJobというダミーの処理を実行しています。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Proxy
{
class Printer : IPrintable
{
private string _name; //名前
//コンストラクタ
public Printer()
{
HeavyJob("Printerのインスタンスを生成中");
}
//引数付きコンストラクタ
public Printer(string name)
{
_name = name;
HeavyJob($"Printerのインスタンス({_name})を生成中");
}
public void SetPrinterName(string name)
{
_name = name;
}
public string GetPrinterName()
{
return _name;
}
public void Print(string str)
{
Console.WriteLine($"=== {_name} ===");
Console.WriteLine(str);
}
//擬似的な処理の重いメソッド
private void HeavyJob(string msg)
{
Console.Write(msg); //メッセージを表示
for (int i = 0; i < 5; i++)
{
try
{
Thread.Sleep(1000); //1秒停止させる
}
catch (ThreadInterruptedException ex)
{
Console.WriteLine(ex.Message);
}
Console.Write(".");
}
Console.WriteLine("完了しました。");
}
}
}
PrinterProxyクラス
代理人を表すクラスです。_realフィールドに、Printerクラスのインスタンスを保持します。SetPrinterNameメソッドやGetPrinterNameメソッドを何回呼んでもPrinterクラスのインスタンスは生成されず、PrinterProxyクラスで処理出来ないPrintメソッドが呼ばれた時に初めてインスタンスを生成します。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Proxy
{
class PrinterProxy : IPrintable
{
private string _name; //名前
private Printer _real; //Printerのインスタンス(本人)
//引数付きコンストラクタ
public PrinterProxy(string name)
{
_name = name;
}
public void SetPrinterName(string name)
{
//Pritnerのインスタンス(本人)が生成されていたら本人にも設定する
if (_real != null) _real.SetPrinterName(name);
_name = name;
}
public string GetPrinterName()
{
return _name;
}
public void Print(string str)
{
Realize(); //Printer(本人)を呼び出す
_real.Print(str);
}
private void Realize()
{
//Printerのインスタンスを生成
if (_real == null) _real = new Printer(_name);
}
}
}
Programクラス
PrinterProxyクラス経由でPrinterクラスを利用する実行用クラスです。最初に佐藤と設定した後、田中に名前を変更しPrintメソッドを呼び出しています。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Proxy
{
class Program
{
static void Main(string[] args)
{
IPrintable iPrintable = new PrinterProxy("佐藤");
Console.WriteLine($"名前は現在{iPrintable.GetPrinterName()}です。");
iPrintable.SetPrinterName("田中");
Console.WriteLine($"名前は現在{iPrintable.GetPrinterName()}です。");
iPrintable.Print("Hello World!");
}
}
}
#実行結果
名前は現在佐藤です。
名前は現在田中です。
Printerのインスタンス(田中)を生成中.....完了しました。
=== 田中 ===
Hello World!
名前の設定や表示の間はPrinterクラスのインスタンスは生成されず、Printメソッドを呼び出したタイミングでインスタンスが生成されたのが確認出来ました。
#考察
Proxyパターンでは、代理人が出来るだけ本人の処理を肩代わりします。例えば初期化に時間がかかる機能が多く存在するシステムがあるとして、起動の時点では利用しない機能まで全て初期化を行うのは無駄です。実際にその機能を使う時に初期化を行うことで効率的なシステムにすることが出来ます。
また代理人クラスを変更することで、何を代理人が処理し何を本人が処理するのかを自由に変更することが出来ます。
DecoratorパターンとProxyパターンの実装は似ていますが、目的が異なります。Decoratorパターンは新しい機能を追加することを目的としているのに対し、Proxyパターンは本人の処理を肩代わりして本人へのアクセスを軽減させることが目的になっています。