デザインパターン 入門

デザインパターン

デザインパターン 入門

  • Basic

    1. Iterator : 1つ1つ数え上げる

    2. Adapter : 一皮かぶせて再利用

  • サブクラス 3. Tempalte Method : 具体的な処理をサブクラスにまかせる 4. Factory Method : インスタンス作成をサブクラスにまかせる

  • インスタンス生成 5. Singleton : たった1つのインスタンス 6. Prototype : コピーしてインスタンスを作る 7. Builder : 複雑なインスタンスを組み立てる 8. Abstract Factory : 関連する部品を組み合わせて製品を作る

  • 分離 9. Bridge : 機能の階層と実装の階層を分ける 10. Strategy : アルゴリズムをごっそり切り替える

  • 同一視 11. Composite : 容器と中身の同一視 12. Decorator : 飾り枠と中身の同一視

  • 構造間の移動 13. Visitor : 構造を渡り歩きながら仕事をする 14. Chain of Responsibility : 責任のたらい回し

  • Simplifying 15. Facade : シンプルな窓口 16. Mediator : 相手は相談役1人だけ

  • 状態管理 17. Observer : 状態の変化を通知する 18. Memento : 状態を保存する 19. State : 状態をクラスとして表現する

  • 無駄をなくす 20. Flyweight : 同じものを共有して無駄をなくす 21. Proxy : 必要になってから作る

  • クラスで表現 22. Command : 命令をクラスにする 23. Interpreter : 文法規則をクラスで表現する

GoFによるデザインパターンの分類

  • 生成に関するパターン

    • Abstract Factory

    • Factory Method

    • Singleton

    • Builder

    • Prototype

  • 構造に関するパターン

    • Adapter

    • Composite

    • Facade

    • Proxy

    • Bridge

    • Decorator

    • Flyweight

  • 振る舞いに関するパターン

    • Chain of Responsibility

    • Interpreter

    • Mediator

    • Observer

    • Strategy

    • Visitor

    • Command

    • Iterator

    • Memento

    • State

    • Template Method

設計に向けて

  • 共通性/可変性分析

  • 分析マトリクス

Details

以下、記載のソースコードは増補改訂版 Java言語で学ぶ デザインパターン入門のURLでThe zlib/libpng Licenseとして配布されているものから、コメントアウトを取り除いたものとして利用させていただいています。

1. Iterator

  • 何かがたくさん集まっているときに、それを順番に指し示していき、全体をスキャンしていく処理を行うためのもの

  • 実装と切り離して繰り返しを行える。使用するのはIteratorのhasNextとnextメソッドのみ。

実装

  • Iteratorの宣言を含む集合体

  • Iteratorの実装

  • 使用方法

2. Adapter

  • すでに提供されているものがそのまま使えず、wrappterして必要な形に変換して利用

  • 以下の2種類

    • 継承

    • 委譲

  • 継承

元々定義されている実装

使用したいインターフェイス

ここで以下のような継承したクラスを実装

  • 委譲

printは抽象クラスとして定義しておく。それ以外は基本同じ。PrintBannerクラスからBannerクラスのオブジェクトを生成し、同クラス内の関数を呼び出す。

3. Template Method

  • スーパークラスで処理の枠組みを定め、サブクラスでその具体的な内容を定義

  • 複数のクラス間でロジックの抽象化

  • サブクラスをスーパークラスと同一視可能

    • スーパークラス型の変数に、サブクラスのインスタンスのどれを代入しても正しく動作するようにする

      • The Liskov Substition Principle(LSP)

抽象化クラス

具象クラス1

具象クラス2

使用方法

4. Factory Method

  • インスタンスを生成する工場をTemplate Methodパターンで構成

  • インスタンスの作り方をスーパークラスの側で定める

抽象化した工場

抽象化した製造物

具象化した工場

具象化した製造物

使用方法

  • 抽象化した工場におけるメソッドの実装方法

    • 抽象化メソッド

    • デフォルトの実装

    • 例外スロー

5. Singleton

  • システムの中に1個しか存在しないものをプログラムで表現したいとき

以下の場合、getInstance()メソッドを呼び出した際に暮らすが初期化されて、そのときにstaticフィールドの初期化が行われる。

  • デメリット

    • ユニットテストがしづらい

    • 複数並列して行われる場合にはシングルトンは使えないかも

6. Prototype

  • new Something()のようにクラスからインスタンスを作るのではなく、インスタンスを複製して新しいインスタンスを作る

  • 例えば、以下のような状況

    • 種類が多すぎてクラスにまとめられない場合

    • クラスからのインスタンス生成が難しい場合

    • フレームワークと生成するインスタンスを分けたい場合

複製される対象のクラスはCloneableインターフェイスを実装する必要があります。 このCloneableインターフェイスは、マーカーインターフェイスと呼ばれるもので、メソッドが一つも定義されておらず、cloneによってコピーすることができるという印のみに使用されます。Cloneableインターフェイスを実装しない場合、CloneNotSupportedExceptionの例外が発生します。

Cloneableインターフェイスを継承したインターフェイスを用意。実際には、コピー対象のクラスやそのスーパークラスで直接実装しても問題ありません。

Managerクラスの実装

使用方法

7. Builder

  • Builderクラスは、インスタンスを作成するためのインターフェイスを定義し、そこからそれを実装した各種具象クラスを用意し、Director役のクラスがそれらの具象クラスのメソッドを組み合わせて、目的のものを作り上げる。

Builderクラス

具象クラスの例

Directorクラスで具象クラスのメソッドを組み合わせて目的のものを作り上げる。

使用方法

8. Abstract Factory

  • 抽象的な工場で、抽象的な部品を組み合わせて抽象的な製品を作る

  • 部分の具体的な実装には着目せず、インターフェースに着目し、そのインターフェイスだけを使って、部品を組み立て、製品にまとめる

  • 工場の呼び出し元では、具体的な部品、製品、工場等は利用せず、java Main listfactory.ListFactoryコマンドのように呼び出すクラスを引数で与えており、Class.forName(classname).newInstance()のように生成

抽象的な工場

抽象化部品の例

具体的な工場

具体的な部品の例

呼び出し元

  • クラスの生成方法

    • new : Somethine obj = new Something();

    • clone : obj = (Something)clone();

    • newInstance : (Something) Class.forName(classname).newInstance();, someobj.getClass().newInstance()

  • メリット/デメリット

    • メリット : 具体的な工場を新たに追加するのは簡単

    • デメリット: 部品を新たに追加するのは困難

9. Bridge

  • 機能のクラス階層と実装のクラス階層をわけることで、それぞれのクラス階層を独立に拡張できる

機能の抽象クラス

機能の具象クラス

実装の抽象クラス

実装の具象クラス

使用方法

10. Strategy

  • アルゴリズムを切り替え、同じ問題を別の方で解くのを容易にするパターン

  • 委譲というゆるやかな結びつきでアルゴリズムの切り替えが容易 

Strategy役の抽象クラス

Strategy役の具象クラス

Strategy役を利用するクラス

11. Composite

  • 複数と単数を同一視して、再帰的な構造を作る

Component役となるEntryという抽象クラスで、FileクラスとDirectoryクラスを同一視する。

単数となるFileクラス

複数となるDirectoryクラス

呼び出し元

12. Decorator

  • 中心となるオブジェクトがあり、それに飾り付けとなる機能を一皮一皮かぶせていって、より目的にあったオブジェクトに仕上げていく

  • 飾り枠と中身を同一しており、透過的なインターフェイスを持つ

  • 中身を変えずに機能追加ができる。

呼び出し元

中心となるオブジェクトの元になる抽象化されたクラス

中心となるオブジェクトの元になる具象化されたクラス

飾り付けの元になる抽象化クラス

飾り付けの元になる具象化クラス

13. Visitor

  • データ構造と処理を分離

  • データ構造の中を巡り歩く主体である訪問者を表すクラスを用意し、そのクラスに処理を任せる

  • ConcreateElement役とConcreteVisitor役の組によって実際の処理が決定するというダブルディスパッチ。それぞれのクラスで互いが互いを呼び出している。

抽象化されたVisitor役

具象化されたVisitor役

データ構造側のもので、キーとなるインターフェイスを定義

抽象クラスで実装を宣言

ただし実際には具象クラスで定義

呼び出し元

  • OCP : Open-Closed Principle

    • クラスなどが以下の原則に従うべき

      • 拡張については開かれているが、

      • 修正については閉じられている

    • 既存のクラスを修正せずに拡張できるようにせよ

14. Chain of Responsibility

  • 複数のオブジェクトをチェーンのようにつないでおき、そのオブジェクトの鎖を順次渡り歩いて、目的のオブジェクトを決定する方法

  • 要求する側と処理する側の結びつきを弱めることができ、それぞれを部品として独立させることができる

  • 状況によって要求を処理するオブジェクトが変化するようなプログラムにも対応することができる

使用方法

抽象化クラス。support()中のnext.support(trouble);で次に人に処理を渡す。

15. Facade

  • 関係し合っているたくさんのクラスを適切に制御するために、それらのクラスを個別に制御しなくても、窓口に対して要求を出すだけですむように新たにシンプルなインターフェースを用意する

  • システムの内側にある各クラスの役割や依存関係を考えて、正しい順番でクラスを利用

  • インターフェースを少なくする

Facade役の実装

個々の実装1

個々の実装2

利用方法

16. Mediator

  • 各メンバーはみんな相談役だけに報告し、メンバーへの支持は相談役だけから来るようにする

  • 多数のオブジェクト間の調整を行わなければならないときこそ、Mediatorパターンの出番

  • ConcreteColleague役は再利用しやすいが、ConcreteMediator役は再利用しにくい

呼び出し元

抽象化されたMediatorクラス

具象化したMediatorクラス

抽象化したColleagueクラス

具象化したColleaqueクラス

17. Observer

  • 観察対象の状態が変化すると、観察者に対して通知。状態変化に応じた処理を記述するときに有効

  • Observer役の処理がSubject役の処理に影響を与えるとき、無限連鎖に注意して設計。Observer役に、現在Subject役から通知されている最中かどうかを表すフラグ変数を一つ持たせるのがよい

抽象化したObserver役のクラス

具象化したObserver役のクラス

抽象化したSubject役のクラス

具象化したSubject役のクラス

呼び出し元

18. Memento

  • ある時点でのインスタンスの状態を記録して保存しておき、あとでインスタンスをその時点の状態に戻す

  • Caretaker役はOriginator役にお願いして、現在の状態を表現するMemento役を作ってもらう

  • Caretaker役はMemento役の内部情報は知らないし、気にしない。将来の復元に備えて、大事にMemento役を保存しておく。必要なときに引き出しの奥からMemento役を取り出して、Originator役にわたすと、きちんと復元される

  • Caretaker役とOriginator役

    • Caretaker役 : どのタイミングでスナップショットを撮るかを決め、いつUndoするかを決め、Memnto役を保持 

    • Originator役: Memento役を作る仕事と、与えられたMemento役を使って自分の状態を元に戻す仕事

Caretaker役で現在のOriginator役の状態を保存したいときに、そのことをOriginator役に伝える

Originator役で、Memento役を作ったり、リストアしたりする。

Memento役で、Originator役の内部情報をまとめるクラス

  • Memento役

    • wide interface

      • オブジェクトの状態をもとに戻すために必要な情報がすべて得られるメソッドの集合

      • これを使えるのはOriginator役のみ

    • narrow interface

      • 外部のCaretaker役に見せるもの

      • 内部状態が外部に公開されるのを防ぐ

19. State

  • 状態をクラスで表現。個々の具体的な状態を別々のクラスとして表現して問題を分割

  • 新しい状態を追加するのが簡単

  • 状態遷移を状態に依存した振る舞いとして見なし、個々の具体的な状態のクラスのメソッド中で他のクラスを呼び出すメリット/デメリット

    • 他の状態に遷移するのはいつかという情報が、1つのクラス内にまとまっている

    • 1つのConcreateState役が他のConcreate役を知らなければならない

抽象化したState役のクラス

具象化したState役のクラス

抽象化したContext役のクラス。現在の状態を表すConcreteState役を持つ。

具象化したContext役のクラス

20. Flyweight

  • インスタンスをできるだけ共有させて、無駄にnewしない

  • 管理されているインスタンスはガベージ対象になれない点について注意

Flyweight役。普通に扱うとプログラムが重くなるので共有した方がよいもの。

FlyweightFactoryの役。Flyweight役を作る工場。

Clientの役。FlyweightFactory役を使ってFlyweight役を作り出し、それを利用する役。

21. Proxy

  • 実際に必要な段階になってはじめて、対象のクラスを生成する。

Proxy役とRealSubject役を同一視するためのインターフェースを定義

RealSubject役。重い処理の想定。 

Proxy役はClient役からの要求をできるだけ処理。もし、自分だけで処理できず、必要になってからはじめてProxy役はRealSubject役に仕事を任せる。

Client役。Proxyパターンを利用

22. Command

  • 命令をオブジェクトとして表現することで、履歴を取ったり再実行を行ったりすることができるようになる

抽象化されたCommand役

具象化されたCommand役

命令の受け取り手となるReceiver役

上記インターフェイス

ConcreteCommand役を生成し、その際にReceiver役を割り当てるClient役

  • 命令の実行を開始するInvoker役。Command役で定義されているインターフェースを呼び出す役

    • Mainクラス(ConcreteCommand役を生成し、その際にReceiver役を割り当てるClient役も兼ねる)

    • DrawCanvasクラス(命令の受け取り手となるReceiver役も兼ねる)

23. Interpreter

  • プログラムが解決しようとしている問題を簡単なミニ言語で表現

AbstrtactExpressionの役。構文木のノードに共通のインタフェースを定義する役。

TerminalExpressionの役。BNFのターミナル・エクスプレッションに対応する役。

NonterminalExpressionの役。BNFのノンターミナル・エクスプレッションに対応する役。以下が例。他も例多数。

Contextの役。インタプリタが構文解析を行うための情報を提供する役。

Client役。構文木を組み立てるために、TerminalExpression役やNonterminalExpression役を呼び出す役。

マルチスレッド

マルチスレッド

Overview

  1. Single Threaded Execution - この橋を渡れるのは、たった一人

  2. Immutable - 壊したくとも、壊せない

  3. Guarded Suspension - 用意できるまで、待っててね

  4. Balking - 必要なかったら、やめちゃおう

  5. Producer-Consumer - わたしが作り、あなたが使う

  6. Read-Write Lock - みんなで読むのはいいけれど、読んでる間は書いちゃだめ

  7. Thread-Per-Message - この仕事、やっといてね

  8. Worker Thread - 仕事が来るまで待ち、仕事が来たら働く

  9. Future - 引換券を、お先にどうぞ

  10. Two-Phase Termination - 後片付けしてから、おやすみなさい

  11. Thread-Specific Storage - スレッドごとのコインロッカー

  12. Active Object - 非同期メッセージを受け取る、能動的なオブジェクト

詳細

  • Single Threaded Execution - この橋を渡れるのは、たった一人

    • synchronizedで同時に1スレッドからしか実行できないようにする

    • アトミックな操作

      • 基本型・参照型の代入・参照はアトミックな操作

      • ただし、long,double型の代入・参照はアトミックな操作ではない

      • long,doubleをスレッド間で共有する場合、synchroizedの中に入れるかvolatileとして宣言

    • ある領域を一つのスレッドだけに限定していたところをN個までに広げたものを計数セマフォ

  • Immutable - 壊したくとも、壊せない

    • set系メソッドのないクラス

      • 以下のような工夫で子のクラスから変更されることを防止

        • finalとして宣言

        • フィールドはprivate

  • Guarded Suspension - 用意できるまで、待っててね

    • 自分の状態が適切な状態のときだけ、目的の処理をスレッドに実行させる

      • オブジェクトが適切な状態であることをガード条件で表現

    • 呼び方

      • guarded suspension

      • guarded wait

        • 条件のテストにwhileを使い、待つためにwaitメソッドを使用。条件が変化したら、notify/notifyAllメソッドを使用

      • busy wait

        • スレッドがwaitで待つのではなく、yieldしながら条件をテストするような実装方法

      • spin lock

        • 条件が成り立つまでwhileループで回って待つ様子を表現

      • polling

        • あるイベントが起こるのを繰り返し調べに行き、起こったらそれを調査する方法

    1. Balking - 必要なかったら、やめちゃおう

    2. オブジェクトが適切な状態であることをガード条件で表現

    3. ガード条件が満たされているときに限り、実行を継続。ガード条件が満たされていなければ、処理を実行せずにすぐにメソッドから帰る。

    4. タイムアウト

      • synchronizedブロック

        • タイムアウトもinterruptもできない

      • waitを実行してウェイトウェットの中にある状態

        • guarded timed

          • BalkingとGuarded Suspensionの中間でガード条件を満たすまで一定時間待つ

          • スレッドがオブジェクトのウェイトセットに入って停止した状態から抜け出す

            • 方法

              • notify : インスタンスに対して実行

              • notifyAll : インスタンスに対して実行

              • interrupt : スレッドに対して実行

              • wait(タイムアウト値)

            • notify/notifyAllとタイムアウトの場合を識別する方法はない。interruptの場合はInterruptedExceptionの例外が投げられる

            • 詳細

              • waitの場合でもsleepの場合でもjoinの場合でも同様にinterruptで中断できる

                • throws InterruptedExceptionがついている

                • 時間はかかるけどしれないがキャンセルできる

              • notify/notifyAll

                • java.lang.Objectクラスのメソッドで、そのインスタンスのウェイトセットの中にあるスレッドを起こす

                • スレッドを直接指定するものではなくインスタンスのロックを撮る必要がある

                • notifyについてウェイトセットの中にあるスレッドのどれを選択するかは仕様で定められておらず、Javaの処理系に依存

              • interrupt

                • インタラプト状態にするメソッド

                • InterruptedExceptionが指定されているメソッドは時間がかかるかもしれないが処理をキャンセルできる

                • java.lang.Threadクラスのメソッドでスレッドを直接指定して呼び出すもの

              • interrupted

                • インタラプト状態のテストとクリアを行うメソッド

              • stop

                • Threadクラスのstopメソッドはdeperecated

                • 安全性を壊す可能性があるため使ってはいけない

                  • スレッドがクリティカルセクションを実行している途中であってもいきなり終了させることができる

      • java.util.concurrent

        • 例外でタイムアウト

          • Futureインターフェイスのgetメソッド

          • Exchangerインターフェイスのexchangeメソッド

          • CyclicBaririerインターフェイスのawaitメソッド

          • CountDownLatchインターフェイスのwaitメソッド

        • 戻り値でタイムアウト

          • BlockingQueue

          • Semaphore

          • locks.Locks

    1. Producer-Consumer - わたしが作り、あなたが使う

    2. Data役を作るProcuder役のスレッドから、Data役の中継地点、橋渡し、通信路の働きをするChannel役に渡したいData役を保持させて、Data役を使うConsumer役のスレッドにデータを渡す

    3. Channel役

      • 種類

        • キュー

        • スタック

        • 優先度付きキュー

      • java.util.concurrent

        • BlockingQueue

        • ArrayBlockingQueue

        • ConcurrentLinkedQueue

        • Exchanger

    1. Read-Write Lock - みんなで読むのはいいけれど、読んでる間は書いちゃだめ

    2. readとwriteという2つの処理を提供するSharedResource役がいる。readはSharedResource役の状態を変更しない処理でwriteは変える処理

    3. read中に他のreadに対するロックを他の組み合わせと同様にロックするとリソースがもったいなく排他制御が不要

    4. そのため、read用のロックとwrite用のロックを別々に提供するReadWriteLock役を導入

    1. Thread-Per-Message - この仕事、やっといてね

    2. Client役はHost役のrequestメソッドを呼び出して要求を出す。その要求を実際に処理するためのHelper役のhandleメソッドがある。このとき要求を処理するために、新しいスレッドをHost役の中で起動して、新しいスレッドがhandleの呼び出しを行うことで、非同期のメッセージ送信を実現

    3. 応答性を上げ、遅延時間を下げる

    4. 処理の順序が問題にならず、戻り値が不要な場合に使用する

    1. Worker Thread - 仕事が来るまで待ち、仕事が来たら働く

    2. Thread-Per-Messageのようにリクエストのたびに新しいスレッドを起動するのは無駄なので、仕事を実行するスレッドを予め起動しておき、Producer-Consumerを使って、仕事の内容を表すインスタンスをワーカースレッドにわたす

    1. Future - 引換券を、お先にどうぞ

    2. Thread-Per-Messageでは処理を任せた時点では結果を得られなかったが、処理の結果を表すRealData役と同じインターフェイスAPIを持つFuture役を作る

    3. 処理の開始時点ではFuture役を戻り値として、別のスレッドの処理が完了したら、その結果をFuture役にセット。Client役はFuture役を使って処理の結果を得る

    1. Two-Phase Termination - 後片付けしてから、おやすみなさい !

    2. 概要

      • 動作しているスレッドを終了させ、終了する前には、特定の終了処理を行う場合、オブジェクトの安全性を失わせないために終了要求を表すメソッドを用意

      • そのメソッドの中で終了要求がきたというフラグを立てる。スレッドは終了処理をはじめても良い安全なポイントでそのフラグをテスト。終了処理への移行はあくまで終了するスレッド自身の判断で行う。

      • Javaではwait,sleep,joinの各メソッドで待っているスレッドを中断させるため、interruptメソッドを使用。Thread.interruptedメソッドを呼び出すとインタラプト状態がクリアされるので、終了要求のテストには注意

      • 実行時に例外がおきたときでも確実に終了処理を行わせるにはfinallyを使用する

    3. スレッドの終了方法 !!!

      • 非推奨

        • java.lang.Threadのstopメソッドは、スレッドを強制終了するが安全性が保証されずdeprecated

          • java.lang.ThreadDeathの例外を投げる

      • 推奨

        • shutdownRequestメソッドでshutdownRequestedフラグ判定だけを入れつつ、interruptメソッドも使用

        • 理由

          • shutdownRequestメソッドでshutdownRequestedフラグ判定だけではなくinterruptメソッドを使用しているのは、スレッドがsleepしているかもしれないから

          • 逆にinterrupstメソッドのみでshutdownRequestedフラグ判定を入れないと、一箇所でもInterruptedExceptionの例外をcatchしたところで何も処理しないと、インタラプト状態ではなくなり要求がなかったことになる。フラグは終了要求が出されたことを記録しておくためのもの

    1. Thread-Specific Storage - スレッドごとのコインロッカー

    2. 対象となるオブジェクトをTSObject役とし、TSObject役と同じインタフェース(API)を持つTSObjectProxy役を作る。Client役→TSObject役の対応表を管理するためにTSObjectCollection役を用意。TSObjectProxy役はTSObjectCollection役を使って、現在のスレッドに対応するTSObjectを取得。そして、そのTSOBject役に処理を委譲

    3. joinメソッドとisAliveメソッド

      • 指定したスレッドの終了を待つにはjava.lang.Threadのjoinメソッド

      • 指定したスレッドが現在終了しているかどうかはjava.lang.ThreadのisAliveメソッド

      • 指定したスレッドの状態を取得するにはjava.lang.ThreadのgetStateメソッド

    4. java.util.concurrent.ExecutorService

      • shutdown : 動作しているスレッドをきちんと終了

      • isShutdownメソッド : shutdownメソッドが呼ばれたか調べる

      • isTerminatedメソッド : スレッドが実際に止まっているかどうか調べるメソッド

    1. Active Object - 非同期メッセージを受け取る、能動的なオブジェクト

    2. 処理を依頼する人(Client役)と、処理を実行する人(Servant役)がいる状況について

      • Servant役が処理を実行するのに時間がかかったり、処理を実行できるタイミングが遅くなったりしてもClient役に影響を与えたくない場合

      • Client役からServant役への一方向の呼び出しだけではなく、実行結果をServant役からClient役へ返すことができるように双方向の呼び出しにしたい場合

      • 処理の依頼順序と処理の実行順序を独立にしたい場合

    3. 処理

      • Client役からの依頼はProxy役へのメソッド呼び出しとして実現。Proxy役はその依頼をConcreteMethodRequest役という1つのオブジェクトに変換し、Scheduler役を経由して、ActivationQueue役に保存

      • ActivationQueue役から次に実行すべきConcreteMethodRequest役を選び出して実行するのは、Scheduler役の仕事。Scheduler役はClient役とは別のスレッドで動作し、ConcreteMethodRequest役経由で、Servant役に処理を委譲

  • volatile

    • 同期化 : あるスレッドがvolatileフィールドに行った書き込みは他のスレッドからすぐに見える

    • longとdoubleのアトミックな扱い

  • 複数のスレッドで共有されるフィールドは、synchronizedまたはvolatileで守る

Last updated