LoginSignup
39
52

More than 1 year has passed since last update.

C言語で デザインパターンにトライ! デザインパターン一覧

Last updated at Posted at 2018-04-29

Wikipediaベースに、自分の言葉で概要を整理しました。自分の投稿記事もここにまとめていきます。

参考:
Wikipediaデザインパターン

TECHSCORE(テックスコア) デザインパターン

特に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();
    }
}

39
52
2

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
39
52