17
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

デザインパターン勉強会 第9回:Bridgeパターン

Posted at
1 / 12

デザインパターン勉強会 第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を作成するような場合です。
いわゆるクラスの継承による機能拡張を指します。

・「実装のクラス階層」とは
親クラス(またはインターフェース)で規定された抽象メソッドを、子クラスで実装するような場合を指します。
実装方法の切り替えを実現するための階層関係です。

#アンチパターンの例
機能と実装の階層を分離しない場合、以下のようなクラス階層となることが考えられます。
Bridge3.png

①AbstractClassの内部処理ExecuteInnerProcを実装する2つのクラスを作成し、実装の切り替えを可能にしていました。(実装の階層)
②AbstractClassを拡張したAbstractBetterClass作成した場合、それを継承して内部処理を実装するクラスを新たに2つ作成する必要が生じてしまいます。(実装の階層 * 機能の階層)

このような構成では、クラスの数が機能と実装の各種類数の積となってしまいます。
機能の拡張や実装の種類の追加がこの先も発生すると考えた場合、クラス数が積算で増加してしまうこの構成は良くありません。

前述の通り、Bridgeパターンはこの「機能のクラス階層」と「実装のクラス階層」を分離した上で、橋を渡すように結びつけるパターンです。

#Bridgeパターンのサンプルプログラム
##クラス図
Bridge.png

機能の階層
 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を実行した結果が以下の通りです。
Bridge2.png


まとめ

  • Bridgeパターンは機能の階層と実装の階層を分離し、クラス階層を簡素化するためのパターン
  • 機能の上位クラスが、集約している実装の上位クラスに処理を委譲することによって、2つの階層を橋のように結びつける
  • 今回の例では、機能クラスの生成時に実装クラスを渡している。(→DIパターン)

17
12
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?