Wikipediaベースに、自分の言葉で概要を整理しました。自分の投稿記事もここにまとめていきます。
特にTECHSCOREさんの解説は利点がわかりやすい気がします。
私の勉強 && ライブラリ作成用なので、表の右2列に自作Cライブラリとその記事に関する情報も載せています。ご了承ください。
パターン名にwikiのリンクを張っています。
これをベースに作ったライブラリパッケージの紹介がこちらになります。
作成ライブラリ本体はこちらはとなります。
生成に関するパターン
パターン名 | ざっくり認識 | ライブラリ作成予定 | 自分でまとめたQiita記事 |
---|---|---|---|
Factory Method | 利用者は抽象クラスCreaterのInterfaceを利用。 Createrでは抽象クラス/インターフェイスProductを利用する。 Productの実体生成はCreaterクラスの実装クラスに委ねる。 |
No, CreatorとProductの関係性が大事 | FactoryMethodパターン |
Abstract Factory | Objectインスタンスの生成を1クラスのAPIにまとめ、さらにそのクラスも抽象化して実体を差し替えやすくする。 Factory MethodのFactoryも抽象化してしまおう!と考えると分かりやすい。 |
No, インスタンスありき | Abstract Factoryパターン |
Builder | 元々は複雑なconstructをラップするためのもの。 DirectorがBuilderインターフェイスのインスタンス&インターフェイスを包含。 Director利用者はconstructを呼ぶだけでまとめてBuilder達の処理実行が行われる。 |
形を変えれば。参考 | その2.Builderパターン |
Prototype | クラスの状態を含めて一旦プロトタイプとして登録し、共有する。 初期化に時間のかかる処理を実行した上で登録。 みんなはそれを利用する形。 リソースとしては別だけど、以前の結果をうまく利用するような構成に見える |
参考 | protottypeパターン |
Singleton | いつクラスのインスタンスを取得しても、同じインスタンスが取得できるようにする。 Flyweight パターンに包含される。 |
- | 無し |
構造に関するパターン
パターン名 | ざっくり認識 | ライブラリ作成予定 | 自分でまとめたQiita記事 |
---|---|---|---|
Adapter | 既存のインターフェイスを変更、利用した新規インターフェイスクラスを実現したい場合に利用。 既存クラスを利用したインターフェイス実装クラス(Adapter)を作り、既存クラスを変更せずに済ませる |
No, どうシステムを拡張するかによりそうなので | ベースのIFが無いとしょうがないので深堀はなし。 |
Bridge | 利用されるAbstractionクラスに使うメソッドを全部用意せず、別インターフェイスクラスInplementorを用意。 AbstractionクラスはInplementorを保持して呼び出す形にして、役割分担をすっきりさせる |
No, 使い手の設計構造に依存するためライブラリに向かなそう | bridgeパターン |
Composite | クラスでの木構造表現 | 木構造化はそこら中にある | 無し |
Decorator | インターフェースに対して、ベースとなる実装クラスと、インターフェース実体を保持する装飾(Decorator)クラスを用意。(装飾クラスは各メソッド実行時に自処理⇒保持している実体の処理を実行) ベース/装飾クラスをコンストラクタで渡しつつ装飾クラスを生成していくことにより、インターフェースのメソッドを装飾し拡張していく。 |
No, IFありきなので抽象的に表現しても面白くない | Decoratorパターン |
Facade | 単純な操作だけを持ったFacadeクラスを用意してサブシステムを実現 | No, 設計の仕方のなので | 深堀の為に作るかも |
Flyweight | "同値"とみなしたインスタンスを同じインスタンスを利用して共有する。 | 参考 | その1. Flyweight パターン |
Proxy | 出力を変えないよう咬ませるラッパー。 HTTPサーバーで出てくるProxyサーバーのシステム版 |
No, ラップするProxy側の自由度が魅力なので | その4.Proxyパターン |
振る舞いに関するパターン
パターン名 | ざっくり認識 | ライブラリ作成予定 | 自分でまとめたQiita記事 |
---|---|---|---|
Chain of Responsibility | (主に同一IFで?)同一処理を行うオブジェクトをリスト化。 各オブジェクトは処理実行時に自分が実行できるかを判定し出来れば実行、出来なければ次に委譲(or Stop)。 Androidのボタンイベントみたいなイメージ。 |
参考 | その5. Chain of Responsibility パターン |
Command | 利用者にはコマンドクラスを介して出来るだけ簡単に機能を使ってもらおう! コマンド実装も渡される関連クラスを利用して実現する形にして、各個変更しやすくしよう! |
No, 考え方は使えそう | commandパターン |
Interpreter | その分野に特化して超効率的な処理実装を行う | その分野の専門家に任せましょう | 無し |
Iterator | 言語でよくあるIteratorと同じ | 使いたいならC++を使おう | 無し |
Mediator | Colleague(Object達)の管理InterfaceであるMediatorを用意。 使用者はMediatorを介してColleagueを利用。 Colleagueとその使用者の結合度を下げる。 |
No, 使えるシーンはあるが、汎用的にするものではない | その6. Mediatorパターン |
Memento | ロールバック出来るようにする。 | 参考 | Mementパターン |
Observer (出版-購読型モデル) | イベント登録しておくことで、イベント発生時にスムーズにイベント処理を行う | 参考 | その3. Observer パターン(出版-購読型モデル) |
State | 同じメソッドの振る舞いを、状態ごとに変える。 Cでもよくやるstate関数テーブルみたいなもの。 イベント動作も踏まえて状態遷移表(StateMachine)を実現した形にするとまた面白い |
参考 | その7.Stateパターン |
Strategy | コンストラクタやsetメソッド等でStrategyを必要に応じて変更出来るようして、 元のクラスインスタンス/メソッドはそのままにそのメソッドのアルゴリズム(戦略)を選択可能とする |
No, 戦略の与え方、使い方の問題。ライブラリでやることではない | Strategy パターン |
Template Method | abstract classで実装されているメソッド内で、抽象メソッドを実行。 コアとなる共通処理は抽象メソッドに委ね、その実装により処理を変えられるようにする。 |
No, 実現するならライブラリ差し替え的な使い方の方がわかりやすそう。 | 深堀の為に作るかも |
Visitor | 操作に関わる部分とデータに関わる部分を分離して、操作の追加がデータ側に影響ないようにする かな | No, 構成ありきに見える | 深堀の為に作るかも |
マルチスレッドに関するパターン
2018/06/01 ちょうどThread poolをまとめたかったので追加
マルチスレッドのパターンについては、ほとんど「排他制御とは」を丁寧に説明すれば説明がつく気がするので、まとめて1つの記事で紹介します。
とりあえず排他系の処理に関してはpthreadのmutexやセマフォ、DBのトランザクション(私はsqliteくらいしかちゃんと把握していない)といったものに対する排他を思い浮かべていただけるといいかと思います。
パターン名 | ざっくり認識 | ライブラリ作成予定 | 自分でまとめたQiita記事 |
---|---|---|---|
Active Object (Actor) | メソッドの呼び出し即実行ではなく、リクエストはメッセージキュー等に積みActorが処理を実施。 | - | |
Balking | 前提条件が満たされていない場合は、(その時点での)処理の実行をあきらめる。 2重起動防止や、起動シーケンスの際にレイヤーの違うアプリケーションの状態を気にせずinitializeを投げまくるようなケースが近い気がする。 |
- | - |
Double-checked locking | 「pthread_mutex_trylock 」、「sqliteでのBEGIN IMMEDIATE 中の別のプロセス/スレッドからのBEGINへのエラー」といった、ロック中がチェックしてからロックをするようにした手法。 エラーでなければロックするの説明から pthread_mutex_trylock が一番近いと思われる。 |
- | |
Future | 別スレッドとの同期が必要な値取得の際に、いつになるかわからないけど結果が更新されてから取得するようにする。 読み込み/書き込み両方を排他したデータのgetと考えればいいかと |
- | - |
Guarded suspension | 前提条件を満たすまで待つ機構。 Futureと似た感じだけど、Futureは排他されたデータ取得し、使用者側はpthread_join/cond_signalやメッセージでトリガーをもらう。 など、別スレッドの完了をトリガーに動作を開始する機構に見える |
- | - |
Lock | 純粋な読み込み/書き込みの排他制御 | - | - |
Monitor | メソッドに対するスレッドセーフな仕組み。 Javaの言語レベルでのサポートはsynchronized のことだと思う。Lockはメソッドや対象の塊で排他しましょうねって話と理解 |
- | - |
Producer-consumer | 要はsocketのread/writeやmqueue, WebSocketのような通信を利用した同期方法。 確かに同期処理という意味でも、仕組み上で排他を意識せずに同期がとりやすいのかも |
- | - |
Reactor | 情報ちょうだい。結果は後でいいからって考え。要はコールバック。 Producer-consumerと組み合わせるのが個人的に楽 |
- | - |
Readers-writer lock | 書き込みだけ排他。読み込みはOKというパターン。 取得時に厳密な同期の必要ない、例えばそのうち拾えればいいイベントの定期取得等は、書き込みの瞬間とかぶって前の結果が取れても大事にはならないのでこのパターンで十分。 |
- | - |
Scheduler | 非スレッドセーフな処理に対して、各スレッドに「いつなら実行OK」と割り当てを行うってことだと思う。 | - | - |
Thread pool | 大量の非同期処理が必要な場合に、全てを単純にスレッド化するとメモリが大変なことになる or 効率が悪い。 なので効率のいい数だけスレッドを作成し、その上で処理を実行してもらう仕組み。 何も考えずに実現すると実際にどのスレッドが実働しているのかがわからないことが起因して混乱を招くので注意。 |
参考 | Threadpool |
Thread-specific storage | スレッドごとに違う値を持つstatic変数を用意できる (同じファイル内のstatic変数を別スレッドで使ってもOKって感じだと思われます)。 |
- | |
Two-phase termination | スレッドを安全に終了させる方法。何も考えずにcancelして強制終了すると大変なことになるので、終了シーケンスはちゃんと作りましょう。 | - | - |
その他:C言語らしいデザイン
2018/06/18追記
上記デザインパターンに該当しないけど、Cらしいデザインを紹介
パターン名 | ざっくり認識 | ライブラリ作成予定 | 自分でまとめたQiita記事 |
---|---|---|---|
メモリプール | あらかじめメモリ領域を確保しておいて、メモリ作成にかかるコストを抑えたりメモリ管理を一元管理する。 | 参考 | こちらで紹介したラッパー等が該当 |
その他気になったものを覚書
パターン名 | ざっくり認識 | 参考 |
---|---|---|
Null Object | if(XX==NULL)なんてコードを書き連ねるくらいなら、何もしないオブジェクトを作ろうぜ | NullObjectパターン |
Dependency Injection | オブジェクトをコンストラクタで注入し、オブジェクトのメソッドを注入されたクラスを介して利用する | やはりあなた方のDependency Injectionはまちがっている。 |
Immutable object | 作成後は状態を変えることのできないオブジェクト。固有なデータだから参照渡しでも安心! | Immutable object |
Active object | 非同期処理で、イベントが来たからその都度すぐに処理をするのではなく、優先度順とか自分に都合のいいタイミングで処理を実行する。 | 【デザインパターン】【非同期】Active Object パターン 記事をクリップする |
Strategyについて
WikipediaのJavaサンプルでは、正直これだとInterface実体を持つの何が違うの?って思いました。
4/30更新 @tenmyo さんからのコメントより、他のwikipediaの例がわかりやすいとのこと
Pythonの例だとコンストラクタに戦略としてメソッドを与えて、on_submitの動作をガラッと変えています。
class Button:
"""A very basic button widget."""
def __init__(self, submit_func, label):
self.on_submit = submit_func # strategy 関数を直接生成
self.label = label
# 異なる戦略を持つ二つのインスタンスを作成
button1 = Button(sum, "Add 'em")
button2 = Button(lambda nums: " ".join(map(str, nums)), "Join 'em")
# ボタンをテストする
numbers = range(1, 10) # A list of numbers 1 through 9
print button1.on_submit(numbers) # displays "45"
print button2.on_submit(numbers) # displays "1 2 3 4 5 6 7 8 9"
英語のJavaの例
setStrategy
で途中からStrategy変更が可能にしてあり、ハッピーアワーが来たらHappyHourStrategy
に切り替えてますね。
なるほど、同じお客さんに対して、情報コピーとかせずとも時間に応じて注文料金計算のアルゴリズムだけが変えられるわけですね。
個人的にはこんな使い方が面白そうですね!
import java.util.ArrayList;
import java.util.List;
public class StrategyPatternWiki {
public static void main(final String[] arguments) {
Customer firstCustomer = new Customer(new NormalStrategy());
// Normal billing
firstCustomer.add(1.0, 1);
// Start Happy Hour
firstCustomer.setStrategy(new HappyHourStrategy());
firstCustomer.add(1.0, 2);
// New Customer
Customer secondCustomer = new Customer(new HappyHourStrategy());
secondCustomer.add(0.8, 1);
// The Customer pays
firstCustomer.printBill();
// End Happy Hour
secondCustomer.setStrategy(new NormalStrategy());
secondCustomer.add(1.3, 2);
secondCustomer.add(2.5, 1);
secondCustomer.printBill();
}
}