Bridge Pattern

参考リンク

機能と実装の違い

ある数列[1,3,2,5,4]をソートしたいとします.このときソートの方法は何通りかあります.このとき「ソートをする」ということが機能にあたります.そして「ソートをする方法」,たとえばクイックソートやマージソートなどが実装にあたります.クラスを拡張しようとするときには,具体的には「機能の拡張」と「実装の拡張」のいずれかであることが多いです.

Bridge Patternが必要になる状況例

TECSCOREにわかりやすい例が載っていたので,それに添ってBridgeパターンが必要になるシチュエーションを見てみます.ソート機能を持つ抽象クラスSoterに対して,実装クラスQuickSorterBubbleSorterを以下のように作成したとします.機能はSoter,実装はQuickSorter, BubbleSorterに分離されていることに注意します.

現状のコードに新しい「実装」を増やすことは容易いです.単にSoterをextendsした実装クラスを書けば良いだけだからです.しかしながら新しい「機能」を増やすためにはどうしたら良いでしょうか.ためしにソートに加え,それにかかった時間を表示する機能を追加したい場合を考えてみます.するとコードは以下のようになります.

ソートの実装自体は先ほど実装してあるQucickSort, BubbleSortクラスを使いたいとします.しかしながらよくよく考えてみるとTimeSorterにそれらの実装を与えることができません(Java1.7以下での話?).そこで残念ながらそれらのTimeSorterに対応した実装クラスQuickTimeSorterBubbleTimeSorterを書かなければなりません.図で表すと以下のような具合になります.

l-1

このような状況に上手く対処するためにBridge Patternが用いられます.

Bridge Pattern

Bridge Patternでは実装の部分を別の階層に切り離すことによって,これまでに見てきた例のような問題を解決します.まずはコード例を見てみます.

このように機能を実現するための方法がSortImpl委譲されているため,実装の追加はもちろん,機能の追加は容易になります.まとめとしてBridge Patternのクラス図を示しておきます.

l-2

時間経過に伴って機能が拡張されていくバージョンアップの過程と,実装タイプのバリエーションを豊富に広げる品揃えの充実具合を,別個に分けて,整理して管理できる(GoFの23のデザインパターンを,Javaで活用するための一覧表より)

Java8での機能と実装の追加について考えてみた.

この部分については個人的に思いついた内容なので,実際に良い方法なのか微妙なところですが,とりあえず書いておきます.Java8ではインターフェースがデフォルト実装を持てるようになったため,先の例ででてきたSorterを抽象クラスではなくインターフェースとして定義しておけば,「実装の拡張」はもちろん,「機能の拡張」もすっきりおこなえるかなと思いました.ソートにかかる時間を表示するtimeSort機能を追加したあとのクラス的にはこんな具合になると思います.

l-3

現時点でこの方法が抱えている問題は,Sorterが抽象クラスではなくインターフェースになったので状態(プロパティ)を持てなくなってしまったという点だと考えていますが,どうでしょうか.

Strategy Pattern

状況・文脈(context)によって動的に振る舞いを変えたいときの定石がStrategy Patternです.
たとえば各OS向けに文字列を出力するTextPrinterクラスがあるとします.
ところが各OS標準で用いられる改行コードはことなるのでその部分についての振る舞いをOSによって変えなければなりません.
以下はこういう状況のときにありがちなコード例です.

往々にしてif文の嵐になります.そこで改行コード出力のふるまいだけを抜き出して,オブジェクトとして扱ってしまおうというのがStrategy Patternになります(言い過ぎ?).どの環境下でのふるまいも共通して「改行コードを出力する」という責務を持っているのでインターフェースを決めておくと便利です.今インターフェースCompositorを定義し,その後に各々の環境に応じた実装をしていくと良いです.

このようにスッキリしました.今やったことを抽象化したStrategy Patternのクラス図は以下のようになります.

l-4

【参考:GoF本での定義】アルゴリズムの集合を定義し、各アルゴリズムをカプセル化して、それらを交換可能にする。
Strategyパターンを利用することで、アルゴリズムを、それを利用するクライアントからは
独立に変更することができるようになる。

アルゴリズム実装のための専用オブジェクトを複数作っておき,その中から使うものだけを動的に選んで実行する。
ある程度複雑なアルゴリズムになると,ふつうのプログラマであれば,その部分を切り出して集約するだろう。また,他のアルゴリズムに置き変わった時に備えて,互換性も持たせておくだろう。なので,あまり新規性を感じないパターンに思えるかもしれない。とはいえ,アルゴリズムの交換可能性を増すために,共通の基底(または共通のインタフェース)を持たせているという点は注目。(GoFの23のデザインパターンを,Javaで活用するための一覧表より

Bridge PatternとStrategy Patternの違い

よくわからなかったので,GoF本の適用可能性の欄を参照しました.

  • Bridge Pattern(構造に関するパターン)
    • 抽出されたクラスとその実装を永続的に結合することを避けたい場合.
    • 抽出されたクラスとその実装の両方を,サブクラスの追加により拡張可能にすべき場合
    • 抽出されたクラスの実装における変更が,クライアントに影響を与えるべきではない場合
    • 複数オブジェクトの間で実装を共有したい場合
  • Strategy Pattern(振る舞いに関するパターン)
    • 関連する多くのクラスが振る舞いのみ異なっている場合
    • 複数の異なるアルゴリズムを必要とする場合.
    • アルゴリズムが,クライアントが知るべきではないデータを利用している場合.
    • クラスが多くの振る舞いを定義しており,これらがオペレーション内で複数の条件文として現れている場合.

ここから察するに,Bridge Patternは継承より委譲を使うことにより,インターフェースとその実装の結合を弱め,「機能」と「実装」を柔軟に拡張できる構造を作りたいときに使うパターン.そしてStrategy Patternは状況に応じ振る舞いが変わるような状況下でその振る舞いの部分を切り出し,クラスの肥大化・複雑化を避けたり,あるいはクラスと振る舞いの結合を弱めるために使われるのではないかと思いました.間違いがあったらご指摘いただけるとありがたいです.

参考リンク

Singletonパターンの罠

インスタンスが一個しか存在しないクラスを書くためのパターンがSingleton Patternです.登場するのはSingletonクラスひとつのみで以下のようなクラス図になります.

L

結城本には以下のような感じでサンプルコードが掲載されていました.

Double-checked locking 問題

参考ページ

概要

Fooクラスのインスタンスがただ一つだけするようにSingletonパターンを用いて以下のようなコードを書いたとします.

もし2つのスレッドが同時にgetHelper()を呼んだ場合,同時にインスタンスを生成しようとして,片方が完全に初期化されないままのインスタンスの参照を持ってしまうようなことが起こり得ます.これを防ぐためには,コストは大きいですが同期を取ってあげればよく,以下のようなコードに変更すれば良いです(from wikipedia).

よくよく考えてみるとsynchronizeが必要なのはgetHelper()内の最初のif文のみです.そこでDouble-checked lockingというイディオムが考えられました(from wikipedia).

Initialization-on-demand holder idiom

Double-checked lockingによりsynchronizedにより初回に正しくインタスタンス生成されたあとは同期を取らずにすみます.理論的には申し分ないのですが,これはJavaプラットフォームのメモリー・モデルが原因で,期待通りの動作が必ずしも保証されません.Javaではシングルトン実装のアンチパターンとされています.そこでInitialization-on-demand holder idiomというものが推奨されています(from wikipedi).

Template Method Pattern

だいたいの処理の流れが共通しているとき,それをテンプレートとしてスーパークラスに記述して,個々の具体的処理のみをサブクラスに記述するというパターンがTemplate Method Patternです.

このパターンに登場するクラスは主に2つあります.ひとつは,ひな形となるAbstractClassで,ここにはテンプレート内で行う個々の処理を抽象メソッドです.もうひとつは,それらを使った一連の処理の流れを実装したメソッドです.個々の処理はサブクラスで実装するためにprotectedかpublic修飾子を,それらを使った一連の流れを定義したメソッドは全サブクラスで共通化(テンプレート化)させたいのでfinal修飾子を付けます.クラス図は以下のような具合になります.

l

実際にファイルになにかを保存するための一連の流れを想定した例を以下のように書いてみました.

まとめ

  • Template Method Patternはabstract classの有用な使い方のひとつで,「似たような複数の処理の流れ」から共通の性質を見つけ出し,テンプレート化することにより,「似たような複数の処理の流れ」の数が増えたときでも,読みやすいコードが書けるのではないかと思った.
  • ただその「似たような複数の処理の流れ」の共通の性質をしっかりと見抜いて,良いテンプレートを作らないとかえって変更が難しいコードになってしまう危険性も持っているのではないかと思いました.
  • 事例で学ぶデザインパターン 第3回 Template Methodパターン