概要
@hyuki 先生著の『Javaで学ぶデザインパターン入門』(2004年、SB Creative)の1章ずつをベースに、サンプルコードをC#で置き換えながら勉強していく記事です。
※著者の @hyuki 先生には適切に書籍への参照を入れれば問題ない旨ご確認いただいています。
本題
Bridgeパターン
第9回はBridgeパターンです。Bridgeパターンは「(1)機能のクラス階層と(2)実装のクラス階層を橋渡しする」ようなデザインパターンです。
(1)機能のクラス階層と(2)実装のクラス階層とは、基底クラスのAクラスと派生クラスのBクラスがあるとして、
(1)機能のクラス階層: BクラスでAクラスの機能(メソッド)追加をするもの
(2)実装のクラス階層: Aクラスで書いた抽象メソッド(API)をBクラスで実装するもの
という状態のときのクラス間の関係のこととします。
サンプルコード
早速具体的な事例を見てみましょう。『Javaで学ぶデザインパターン入門』(2004年、SB Creative)に掲載されているコードをC#で(大体)書き換えます。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BridgePattern
{
class Program
{
static void Main(string[] args)
{
Display d1 = new Display(new StringDisplayImpl("Hello, Japan."));
Display d2 = new CountDisplay(new StringDisplayImpl("Hello, World."));
CountDisplay d3 = new CountDisplay(new StringDisplayImpl("Hello, Universe."));
d1.Show();
// =>
// +-------------+
// |Hello, Japan.|
// +-------------+
d2.Show();
// =>
// +-------------+
// |hello, world.|
// +-------------+
d3.Show();
// =>
// +----------------+
// |Hello, Universe.|
// +----------------+
d3.MultiDisplay(5);
// =>
// +----------------+
// |Hello, Universe.|
// |Hello, Universe.|
// |Hello, Universe.|
// |Hello, Universe.|
// |Hello, Universe.|
// +----------------+
// 実行が一瞬で終わって確認できないので、キーの入力を待ちます
Console.ReadLine();
}
}
// Abstraction
// ・機能のクラス階層の最上位クラス
// ・Implementorを使用して基本的なメソッドを実装する
// ・機能クラスの階層と実装クラスの階層を橋渡しするImplementerを保持
public class Display
{
private DisplayImpl impl;
public Display(DisplayImpl impl)
{
this.impl = impl;
}
public void Open()
{
impl.RawOpen();
}
public void Print()
{
impl.RawPrint();
}
public void Close()
{
impl.RawClose();
}
public void Show()
{
Open();
Print();
Close();
}
}
// RefinedAbstraction
// ・Abstractionに対して機能を追加
public class CountDisplay : Display
{
public CountDisplay(DisplayImpl impl) : base(impl) { }
public void MultiDisplay(int times)
{
Open();
for (int i = 0; i < times; i++)
{
Print();
}
Close();
}
}
// Implementor
// ・実装のクラス階層の最上位クラス
// ・Abstranctionのインターフェース(API)を規定する
public abstract class DisplayImpl
{
public abstract void RawOpen();
public abstract void RawPrint();
public abstract void RawClose();
}
// ConcreteImplementator
// ・Implementatorを具体的に実装する
public class StringDisplayImpl : DisplayImpl
{
private string str;
private int width;
public StringDisplayImpl(string str)
{
this.str = str;
Encoding sjisEnc = Encoding.GetEncoding("shift_jis");
this.width = sjisEnc.GetByteCount(str);
}
public override void RawOpen()
{
PrintLine();
}
public override void RawPrint()
{
Console.WriteLine($"|{str}|");
}
public override void RawClose()
{
PrintLine();
}
public void PrintLine()
{
Console.Write("+");
for (int i = 0; i <width; i++)
{
Console.Write("-");
}
Console.WriteLine("+");
}
}
}
効能
- 機能と実装のクラスを分けているのでそれぞれのクラス改装を独自に拡張できる
- 機能を追加するとき、実装側を修正する必要はなくすべての実装から追加機能を使用することができる
- 機能クラスでは実装クラスのインスタンスを保持している状態(委譲してる状態)なので、実装の切り替えが容易
使用上の注意
- 機能の拡張は実装の組み合わせで実現できる範囲に限られる
関連しているパターン
感想、疑問、メモ
- マルチプラットフォームのフレームワーク(プラットフォーム毎に使える機能は揃えたいけど実装は異なる)とか、基本的な機能を提供するフレームワーク(階層構造で機能を表現したくて、実装はいろんなバリエーション準備してあげたい)とか大規模なものを開発する局面で使いそうなイメージです。
- 階層構造がある中で機能追加していく、みたいな場面が特に身近なシチュエーションを想像できていないです。
C#で学ぶデザインパターン入門
①Iterator
②Adapter
③Template Method
④Factory Method
⑤Singleton
⑥Prototype
⑦Builder
⑧AbstractFactory
⑨Bridge
⑩Strategy
⑪Composite Pattern
⑫Decorator Pattern