デザインパターン勉強会 第9回:Bridgeパターン
#はじめに
本エントリーは某社内で実施するデザインパターン勉強会向けの資料となります。
本エントリーで書籍「[Java言語で学ぶデザインパターン入門] (http://www.hyuki.com/dp/)」をベースに学習を進めますが、サンプルコードはC#に置き換えて解説します。
第1回:Iteratorパターン
第2回:Adapterパターン
第3回:Template Methodパターン
第4回:Factory Methodパターン
第5回:Singletonパターン
第6回:Prototypeパターン
第7回:Builderパターン
第8回:AbstractFactoryパターン
#Bridgeパターンとは
Bridgeパターンとは、2つのクラス階層、「機能のクラス階層」と「実装のクラス階層」を分離した上で、橋を渡すように結びつけるパターンです。
#「機能のクラス階層」と「実装のクラス階層」
クラスの階層には、「機能のクラス階層」と「実装のクラス階層」の二つの意味があります。
・「機能のクラス階層」とは
あるクラスSomeClassに新しい機能を追加したBetterClassを作成するような場合です。
いわゆるクラスの継承による機能拡張を指します。
・「実装のクラス階層」とは
親クラス(またはインターフェース)で規定された抽象メソッドを、子クラスで実装するような場合を指します。
実装方法の切り替えを実現するための階層関係です。
#アンチパターンの例
機能と実装の階層を分離しない場合、以下のようなクラス階層となることが考えられます。
①AbstractClassの内部処理ExecuteInnerProcを実装する2つのクラスを作成し、実装の切り替えを可能にしていました。(実装の階層)
②AbstractClassを拡張したAbstractBetterClass作成した場合、それを継承して内部処理を実装するクラスを新たに2つ作成する必要が生じてしまいます。(実装の階層 * 機能の階層)
このような構成では、クラスの数が機能と実装の各種類数の積となってしまいます。
機能の拡張や実装の種類の追加がこの先も発生すると考えた場合、クラス数が積算で増加してしまうこの構成は良くありません。
前述の通り、Bridgeパターンはこの「機能のクラス階層」と「実装のクラス階層」を分離した上で、橋を渡すように結びつけるパターンです。
機能の階層
Display
└CountDisplay
実装の階層
DisplayImpl
└NormalDisplayImpl
└StarDisplayImpl
Display(機能の階層)がDisplayImpl(実装の階層)を集約している右向きの矢印が、両者を結びつける「橋」にあたります。
##各クラスの役割
名前 | 役割 |
---|---|
Display | 「表示する」クラス |
CountDisplay | 「指定回数だけ表示する」という機能を追加したクラス |
DisplayImpl | 「表示する」ために使用するメソッドのインターフェース |
NormalDisplayImpl | 通常の表示を実装したクラス |
StarDisplayImpl | 強調した表示を実装したクラス |
##Displayクラス
Displayクラスは、「表示する」機能を提供するクラスです。
「表示する」ためのpublicなExecuteDisplayメソッドが定義されています。
ExecuteDisplayの中では、
・表示の前処理 Openメソッド
・表示処理そのもの printメソッド
・表示の後処理 Closeメソッド
という3つのメソッドの呼び出し順を定義しています。
これらのメソッドの中身では、実際の処理がDisplayImplインターフェースを実装するクラスに委譲しています。
public class Display
{
private DisplayImpl _displayImpl;
public Display(DisplayImpl displayImpl)
{
_displayImpl = displayImpl;
}
protected void Open()
{
_displayImpl.RawOpen();
}
protected void Print()
{
_displayImpl.RawPrint();
}
protected void Close()
{
_displayImpl.RawClose();
}
public void ExecuteDisplay()
{
Open();
Print();
Close();
}
}
##DisplayImplインターフェース
DisplayImplインターフェースは、「表示する」ために必要な手順であるOpen、Print、Closeの各メソッドを宣言しています。
public interface DisplayImpl
{
void RawOpen();
void RawPrint();
void RawClose();
}
##NormalDisplayImplクラス
NormalDisplayImplクラスはDisplayImplインターフェースを実装します。
「文字列を枠線で囲う」という表示方法を実装しています。
public class NormalDisplayImpl : DisplayImpl
{
private string _string;
private int _width;
public NormalDisplayImpl(string s)
{
_string = s;
_width = System.Text.Encoding.GetEncoding("Shift_JIS").GetByteCount(s);
}
public void RawOpen()
{
PrintLine();
}
public void RawPrint()
{
Console.WriteLine("|" + _string + "|");
}
public void RawClose()
{
PrintLine();
}
private void PrintLine()
{
Console.Write("+");
for( int i = 0; i < _width; i++)
{
Console.Write("-");
}
Console.WriteLine("+");
}
}
##StarDisplayImplクラス
NormalDisplayImplクラスはDisplayImplインターフェースを実装するもう一つのクラスです。
「文字列を星で囲う」という表示方法を実装しています。
public class StarDisplayImpl : DisplayImpl
{
private string _string;
private int _width;
public StarDisplayImpl(string s)
{
_string = s;
_width = System.Text.Encoding.GetEncoding("Shift_JIS").GetByteCount(s);
}
public void RawOpen()
{
PrintLine();
}
public void RawPrint()
{
Console.WriteLine("*" + _string + "*");
}
public void RawClose()
{
PrintLine();
}
private void PrintLine()
{
Console.Write("*");
for( int i = 0; i < _width; i++)
{
Console.Write("*");
}
Console.WriteLine("*");
}
}
##Program
動作確認用のテストプログラムです。
・Displayの参照 d1,d2
Displayのコンストラクタに渡すDisplayImplの実装クラスを切り替えることにより、表示する機能の実装が切り替わります。
・Displayの参照 d3
CountDisplayは子クラスなので、親クラスであるDisplayとして使うことができます。
・CountDisplayの参照 d3,d4
拡張機能であるMultiDisplayを使うことができ、コンストラクタに渡すDisplayImplによる実装の切り替えもできます。
class Program
{
static void Main(string[] args)
{
Display d1 = new Display(new NormalDisplayImpl("枠線で表示"));
Display d2 = new Display(new StarDisplayImpl("星で表示"));
Display d3 = new CountDisplay(new StarDisplayImpl("星で表示(子クラス)"));
CountDisplay C1 = new CountDisplay(new NormalDisplayImpl("増幅して枠線で表示"));
CountDisplay C2 = new CountDisplay(new StarDisplayImpl("増幅して星で表示"));
d1.ExecuteDisplay();
d2.ExecuteDisplay();
d3.ExecuteDisplay();
C1.MultiDisplay(5);
C2.MultiDisplay(3);
Console.ReadLine();
}
}
#実行結果
先のProgramを実行した結果が以下の通りです。
まとめ
- Bridgeパターンは機能の階層と実装の階層を分離し、クラス階層を簡素化するためのパターン
- 機能の上位クラスが、集約している実装の上位クラスに処理を委譲することによって、2つの階層を橋のように結びつける
- 今回の例では、機能クラスの生成時に実装クラスを渡している。(→DIパターン)