デザインパターン勉強会 第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パターンはこの「機能のクラス階層」と「実装のクラス階層」を分離した上で、橋を渡すように結びつけるパターンです。
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();
        }
    }
実行結果
まとめ
- Bridgeパターンは機能の階層と実装の階層を分離し、クラス階層を簡素化するためのパターン
- 機能の上位クラスが、集約している実装の上位クラスに処理を委譲することによって、2つの階層を橋のように結びつける
- 今回の例では、機能クラスの生成時に実装クラスを渡している。(→DIパターン)


