14
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

スクリプトの設計についてそこはかとなく1

Last updated at Posted at 2014-11-13

プログラム設計やデザインパターンとのつきあい方を自分なりにまとめていく

#GoFのデザインパターン

系統立ては甘いみたいので、そのことを軽く念頭に。
結局、使う可能性があるパターンはどれなんだろ。

デザインパターンは、よく使われる設計を一般化された形でまとめたものに過ぎない。具体的な実装を提供するものではなく、あくまでもコンセプトとして参照されることが意図されている。サンプルコードは実装例に過ぎない。
デザインパターンはすべての状況における最善の設計では決してない。「Code Complete」は、デザインパターンを紹介している書籍の1つであるが、デザインパターンをむやみに適用するのは不適切であり、不適切な使用はコードの複雑さを無意味に高めてしまうと注意している。[1]
一部のデザインパターンは、プログラミング言語(例: Java, C++)の機能の欠損の印であると主張されることがある。計算機科学者のピーター・ノーヴィグは、GoFによるデザインパターン本の23パターンのうち16パターンは、言語によるサポートによって単純化または除去できることをLispやDylanを用いて実演した。[3]

パターンを使う以前にどういう状況(処理)があるかを考える

WEB

  • パターン化、要素と動作の組み合わせ、条件付け分岐、構造的に派生、グローバル&ローカルな横断的関心

    • ページ遷移
    • バリデーション
    • 単純なDB参照
    • 単純なDB登録・更新・削除
    • 複雑なDB参照
    • 複雑なDB更新処理(トランザクション)
    • ファイル処理・画像処理などのDI的な部分
    • ライブラリの組み合わせや連携
    • データ加工の似通った処理のまとめ
    • メール送信処理
    • ログイン処理
  • リソース注意、リーク注意、軽量、速度、依存性を低く

    • ワーカー処理
    • ルーティング処理
    • テンプレート処理(の呼び出しやコントロールのこと)
    • リダイレクト処理(の呼び出しやコントロールのこと)
  • 軽量、管理のしやすさ、勝手の良さ、呼び出し方法の柔軟性、可読性

    • マジックナンバーや定型の文章の管理
  • どこからでも呼び出せる、可読性、混乱しないための秩序

    • Scaffoldで生成されたような規格化されたクラス(HMVC)などからの処理の呼び出し
  • わかりやすく、軽量に、呼び出しやすく

    • セッション
    • ロギング処理
    • ページング
  • ほかナニカ

    • 気の利いた応用の利く仕組み

列挙してみたものの、むしろ、もうちょっと複合的なオブジェクトを列挙した方が良い気がする。
でもそれって結構無数にあるのかも知れない。
自分の書いたコードをリファクタリングしてみるとかかな。

最初の疑問(traitがDIに変更された事例)

ここで紹介してもらったtraitによる「テンプレートエンジン選択」はDIによる「テンプレートエンジンの注入」に変更されました。traitはクラスとその横断的関心を静的束縛しますが、DIではオブジェクトグラフのコンパイル時に注入されます。

どういうことだろう。traitなど他パターンをつかうメリットもありえるのか、考えたい。
関連するよく分からない用語を調べたりする。引っかかったのは横断的関心。

アスペクト指向(横断的関心に関わる概念)

「相互に関連しあっていて,何か機能変更などがあった場合に 同時に変更されるものは,できるだけ1つのモジュールにまとめておきたい」 という要求がありますが, 何らかの理由でモジュールとしてまとめられない 関心事のことを横断的関心事と呼んでいます.
...
機能特性に対して運用特性という表現も使われています

クラスメソッドにさらにフックするみたいな感じ?

オブジェクトグラフ(横断的関心に関わる概念)

オブジェクトグラフとは、オブジェクトとリレーションシップの集合です。

要するにシリアライズの概念というのは、オブジェクト・グラフを「凍結」して (ディスクに、あるいはネットワーク経由で、あるいは他の手段で) 移動し、そして再度そのグラフを逆に「解凍」して有効な Java オブジェクトにする、ということです。これらはすべて、ほとんど魔法のように行われますが、それは ObjectInputStream クラスや ObjectOutputStream クラス、そして完全に忠実なメタデータのおかげであり、さらにはプログラマーがこのプロセスを選択してクラスを Serializable マーカー・インターフェースでタグ付けしてくれるおかげです。

シリアライズという方法でオブジェクトグラフを扱う手法もあるみたい。
メリット・デメリットがある。

オブジェクトグラフとは、私の理解では、どのオブジェクトがどのオブジェクトに依存するかを表すツリー構造のようなもので、DIの利用が前提となっています。Miško氏の主張は、これらのフェーズを分けることでユニットテストしやすいコードが書けるはず、というものです。
...
つまり、オブジェクトグラフ生成フェーズ(Creation Phase)である createServer() を細かく見れば、コマンドライン引数解釈フェーズ(Parsing Phase)とオブジェクトグラフ生成フェーズに分けられるのではないか、ということです。
...
たぶん、手動DIによるコード例では、Guiceの場合に似せて ServerFactory のコンストラクタにargsを渡しているのでしょう。深い意味はなさそうです。1つめの疑問は解決、ということにしましょう。
2つめの疑問については、DIフレームワークがコマンドライン引数のパースまでしてくれるのであれば、確かに Parsing Phase を意識する必要はなくなります。RubyのDIフレームワークでそこまでしてくれるものがあるのかどうか知りませんが、もしなければ、自作するなり別途 Parsing Phase を用意する必要があるということですね。当面は後者を考えていますが、汎用的にしたくなってDIフレームワーク作りに手を染める未来がすぐそこにある気がします。

オブジェクトグラフという単位でみて処理のフェイズ分けを意識すると良い模様。
DIフレームワークの利用がいいっぽい。

オブジェクトグラフナビゲーション言語というのもあるみたい。

trait,DIコンテナ,サービスロケータ

このへんの違いと意義を理解したい。
。。いろいろ下記に調べたけど整理がつかない。頭に保留。

trait

test.php
trait Attackable {
    function attack() {
        echo '攻撃';
    }
}
 
trait Spellable {
    function spell() {
        echo '魔法';
    }
}
class WarriorWizard extends Human {
    use Attackable, Spellable;
}

traitであれば振る舞いを水平方向に構成できちゃうんです。

サービスロケータ

test.php
$container['infrastructure.mailer'] = function($container) {
    $mailer = new \SendmailMailer();

    return $mailer;
};
$container['domain.transfer.newsletter'] = function($container) {
    $newsletterTransfer = new \NewsletterTransfer($container['infrastructure.mailer']);

    return $newsletterTransfer;
};

オブジェクトBがどのように作られるのかを気にすることなく、完成したオブジェクトBをただ使うという目的を達成するために、コンテナから名前をキーにしてオブジェクトを取り出す、これがサービスロケータのパターンです。

DIコンテナ

test.php
class NewsletterTransfer
{
    protected $mailer;

    public function __construct(\MailerInterface $mailer)
    {
        $this->mailer = $mailer;
    }

    public function send($newsletter)
    {
        $this->mailer->send($newsletter);
    }
}

オブジェクトCがどのように作られるのかを気にすることなく、完成したオブジェクトCをただ使うという目的を達成するために、使いたいオブジェクトの受け取り口だけ用意して、コンテナがそこへ渡してくれる、というのがDIのパターンです

つまり、、どういうことなのか。

traitだとclass定義時に一緒にuseで定義するから静的束縛なのね。
そしてそれは横断的関心も静的束縛されるということになる。と。
なんか見た目はカチッとしてて綺麗だけどなあ。
カチッと束縛したいときはこっちなのかな。どうかね。

DIコンテナとサービスロケータは同じレベルの概念だけど、traitは別のレベルで手法なので同レベルではないのか。
traitを使ったDIコンテナやサービスロケータもあり得るのだろうけどそのあたりはどうなのか。

サービスロケータとDIコンテナはそのあとのタイミングで依存性をつくると。
でもサービスロケータはこの例では配列だし、根幹でルール決めされた中で使うか、
ローカルな処理で限られた範囲で使うかじゃないと混乱しそうな気がする。

DIコンテナは、、コンテナだな。処理が多様に分かれそうならつかっておいて損はなさそうな気がする。

DIとDIコンテナは違う

引数が多くて管理しきれなくなる事は見えてます。
そこで登場するのが DIコンテナ
...
依存関係を定義しておくと、オブジェクトの生成時に依存を解決してくれるという代物です。

DIをするコンテナということか。

DIコンテナとはFactoryパターンの一種で、オブジェクトのインスタンス化方法を管理する手法のひとつです。
類似のものにサービスロケータというものがありますが、こちらはシングルトンパターンによるものなので、DIとは異なります。

コンテナとは

ルールに照らし合わせてみると、Dependency Injection システムだと言っているものの一部は、サービスロケータとして説明する方が適切でした。 一般的には、コンテナ(制御の反転 Inversion of Control コンテナ)かもしれないけれど、それはDIコンテナではありません。 DIコンテナでないことが間違っているのではなくて、 これらを間違った名前で使っていることで、話が混乱して、複雑になってしまうのです。

  1. ポールがどんなルールを作ろうが、ある種の人たちが好きなシステムは本当にDIコンテナなのだ
  2. そんな区別は実際大した問題ではない。どうなっていようが、好きなシステムを使えば良いだけだ。

では、中心となる問題は、どのようにしてプラグインを組み立ててアプリケーションとしてまとめあげるか、ということだろうか? 確かにこれは新種の軽量コンテナが取り組んでいる主要な問題のひとつであり、およそ軽量コンテナと呼ばれるものが皆、 「制御の反転(Inversion of Control: IoC)」を利用して解決しようとしている問題である。
...
思うに、人びとは設定ファイルに色いろ定義しようと欲張りすぎである。 プログラミング言語を使えば、設定メカニズムを素直でパワフルなものに出来ることが多い。 今どきの言語であれば、大規模システム向けのプラグインを組み立てる細ごまとしたアセンブラを簡単にコンパイルできる。 コンパイルが面倒くさいのであれば、スクリプト言語でも同じことを巧くやれるだろう。
...
本記事では、Dependency Injection と Service Locator を利用したサービス設定の基本的な問題に集中してきた。 注目に値する論点が他にもいくつかあるのだが、今はそれを掘り下げる時間がない。 特に取り上げたいのは、ライフサイクルの振る舞いだ。 コンポーネントのなかには、独立したライフサイクルイベントが必要なものもあるだろう(たとえば、サービスの停止や開始)。 これ以外にも、アスペクト指向の考え方を軽量コンテナに採り入れることへの関心の高まりがある。 今のところ、本記事ではアスペクト指向については考慮していないが、今後追加するなり、他の記事を書くなりして、 是非とも取り組んでみたいと思っている。

マーチン・ファウラーさんのDIの成り立ちとかその周辺とかがざっとわかる文章。

制御の反転(Ioc)という概念がコンテナ(軽量コンテナと呼ばれる高度なスタブ)で、オブジェクトの依存関係の注入(DI)を行うコンテナ(Ioc)がDIコンテナとされる、、のかな。
DIの手段としてはDIコンテナでなくサービスロケーーもあり得る。

注入のタイミングをどうするかとか、コンストラクタとセッターインジェクション、設定ファイルかコードのどちらで依存関係を指定するか、とかどう設計するかの視点が示されるが、、頭が追いつかないわ。

近ごろ急増している軽量コンテナのすべてが、共通して基礎としているパターンは、どのようにしてサービスの組み立てを行うかについてのパターン――Dependency Injection パターンである。 Dependency Injection は Service Locator の代替案として有効である。 アプリケーションクラスを構築するにあたっては、両者ともだいたい同じようなものだが、私は Service Locator のほうが少し優勢だと思う。こちらのほうが振る舞いが素直だからだ。しかしながら、構築したクラスが複数のアプリケーションで利用されるのであれば、Dependency Injection のほうがより良い選択肢となる。
...
Dependency Injection を採用した場合は、さらにそのスタイルを選択しなければならない。 私のオススメはコンストラクタ・インジェクションだ。通常はこちらを利用し、 コンストラクタ・インジェクションならではの問題に陥ったらセッター・インジェクションへと切り替えるのだ。 コンテナを新規に構築する場合や、既存コンテナの採用を考える場合には、 コンストラクタ・インジェクションとセッター・インジェクションの両者をサポートできるものを 構築する(または採用する)ことを心がけよう。

とりあえずは、まとめのところに書いてあることに従ったほうがいいのかなと思いつつ保留。

上記のマーチン・ファウラーさんの話の概要が端的にまとまってるNAVERまとめ

DIの実装手法

factoryをinjectする
クラス名をinjectする
プロトタイプをinjectする

Dependency Injection の形式として3つ挙げられている

・PicoContainer でのコンストラクタ・インジェクション
・Spring でのセッター・インジェクション
・インタフェース・インジェクション

マーチン・ファウラーさんはまとめの項では、
コンストラクタ・インジェクション>セッター・インジェクション
とオススメしている。

DIコンテナの実装手法

素のFactoryをDIコンテナとして利用することについて書いてあり、
普段の規模なら実用レベルで使えそうな気がする。
(この手法でカバーできないとされているAOPについてはもうちょっと知りたい。)

この辺、実装につながるような関連事項は下記などで引き続きかんがえていく。

DIコンテナについてもう少し

DIコンテナには2つの大きな目的と、1つの大きな拡張機能があります。それでは1つずつ見てみましょう。
依存性の解決
疎結合のサポート
AOPのサポート

なるほど。

DIコンテナの中身はどうなっているのでしょうか。それは各DIコンテナで全然違いますが、(当たり前ですが)考え方は概ね似ています。

設定フェーズでクラスを集める
要求があったときにインスタンスを生成する
クラスがロードされていなければクラスをロードする
AOPの利用が必要な場合、クラスを拡張してロードする
インスタンス生成時に必要な依存クラスのインスタンスを生成する(再帰的に2へ)
その依存インスタンスを利用してインスタンスを生成する
プロパティの型を走査して、プロパティの型に該当するインスタンスを生成する(再帰的に2へ)
プロパティに生成したインスタンスを設定する
クライアントへインスタンスを返す

という流れになります。インスタンスにはライフサイクルの概念があって、インスタンスが再利用される場合は生成及び設定のフェーズがスキップされます。よくあるライフサイクルは、

シングルトン(一意)
プロトタイプ(都度生成)
リクエスト(APサーバのリクエスト)
セッション(APサーバのセッション)

なるほど。

やっぱDIコンテナ使う人って、とりあえずなんかクラス作るときは
例外なしにインタフェースかぶせとくもんなの?

そうしてもいいぐらい粒の大きいクラスにしかDIしないこと

DIすることとDIコンテナを使うことは区別するべきだと思う。

「setter使えば」が何を意味しているのかイマイチ判然としないけれども、setter使って
サービス等の実装オブジェクトをセットするという意味ならそれは他でもないDIだと思う。

DI自体はDIコンテナの使用の有無に関わらず有用な設計パターンの一つだと思うよ。

あとはブートストラップに実装のsetを列挙するかそれともDIコンテナ使うかの、注入作業
の実装方法の違いに過ぎないと思う。
注入するものとされるものが増えてきて、autowireなど規約による自動注入の類を使い
始めるとDIコンテナも便利だと思う。

なるほど、DIの概念は意識しつつコンテナにするかどうかは粒度をみると。インターフェイスはほぼセットなのかな。

DIは異なるコンポーネント、つまりお互いに帰属関係のない概念を結合する場合に使うべきものです。ドメインモデルと言うものは、同一の概念レベルにあるものではないでしょうか?
アプリケーションのコンフィギュレーションをリソース化する場合の基本は以下のようなものがあります。

横断的/共通的な概念
データソース、データベースの名前、ユーザ名やパスワードなどは、設定ファイルとして切り出す。
外部との接点
既存システムとの連結部、ビジネスルール、サービスの開始部などは、インターフェースでやり取りし、カスタマイズできる部分を設定可能にする。
外部資源
トランザクション、ログファイル、データソースなどはカスタマイズ可能です。あちこちに分散しそうな設定は外部化しておく。

横断的関心に関わる概念とか緩くのり付けすることを意識して設計すると。

そういう意味で照らし合わせると、pimpleとかzendframeworkを例にしたような場合ってのはどう判断されるのか。

http://phpmentors.jp/post/28548053449/pimple-di
http://d.hatena.ne.jp/noopable/20090412/1239484575

多分、ニュースとメールという異なるコンポーネントだったり、フロントコントローラーという特殊なモノなので沿っていると考えていいのかな。

LaravelとDI

LaravelはDIによるオブジェクト連携を基礎コンセプトにしているので、
深くDIについて知ることでより効率的にアプリケーションを構築していくことが出来ます。
こちらの川瀬さんによる連載が非常に素晴らしいので、お読みになることをオススメします。

そうなのか、、Laravelに興味がわきました。

参考になりそうなサイトメモ

便利そうなリンク

デザインパターンの特性がよくわかる
ファクトリー・シングルトン・ストラテジー・フロントコントローラ・MVC(HMVC や MVVM)の説明

これで既存のメソッドをオーバーライドすることができました。
長い決まりきったコードをコントローラから追い出せるので、だいぶ見通しが良くなりそうですね。しかも、決まった名前のメソッドなので中身を読まないと意味がわからなかった accessRules なんてものが、MemberCanEdit という意味のある名前になりました。

traitの使い方(オーバーライドできる)

特にトレイト名を文字列で与えているあたりが微妙だ。
トレイト名はクラス名のようには扱うことができず、use節以外で使用するとUse of undefined constantのNoticeになってしまうようです。

traitの使い方(インターフェイスとしては微妙)

  1. アスペクト指向・横断的関心事・Dependency injection
  2. フィーチャ指向・文脈指向
    ...
  3. 型安全性2 (多層性・総称型・The Expression Problem・モジュール型)
    ...
  4. メタオブジェクトプロトコル

面白そうな講義だなあ。気になる用語をピックアップ。

PHPによるデザインパターン入門
Gof本

http://www.ibm.com/developerworks/jp/opensource/library/os-php-designptrns/
http://qiita.com/massa142/items/2a80789ed2998ab14f06
https://ja.wikipedia.org/wiki/%E3%83%87%E3%82%B6%E3%82%A4%E3%83%B3%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3_(%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2)

でざいんぱたーんについて

DIコンテナとデザインパターン

• DIコンテナに向いている機能
– オブジェクトを生成する。
– オブジェクト同士の依存関係を解決する。
– 実装クラスを意識したくない。
– 実装クラスを交換可能にする。
• DIコンテナに向いていない機能。
– オブジェクトの状態が変わる。
– 一時的なオブジェクト。
– オブジェクトの変換。

ざっくり要約すると「クラスの中でnewしてはいけない。必要なインスタンスは外から突っ込むべし」というところかな。
...
うーん。確かに、DBやLoggerなど、クラスの中で一つだけあれば十分なものも多く、そういうオブジェクトはコンストラクタに渡す形で「外から突っ込む」ことができるでしょう。
しかし、動的にオブジェクトを作るケースだって山ほどあるはずです。「必要なオブジェクトはクラスの生成時に全部できあがっている」なんて状態はそうそうあるもんじゃありません。

歴史経緯
古典>Strategy>DIコンテナ

PHPのDIコンテナのpimple

今回は、PHPで使えるDIコンテナ(Dependency Injection Container)のうち、非常にシンプルな実装であるPimpleを使い、DIの例と、陥りやすいDIではない例を紹介します。

インタフェースの実装クラスを差し替えることが容易にできるようになるので、テスト時におけるオブジェクトのモックやスタブへの差し替えもずっと楽に行えるようになります。

DIコンテナによる保守性の向上、モックやスタブへの差し替え

オブジェクト指向の法則集 - Qiita
http://goo.gl/HaPN5l

デザインパターンの話 - Qiita
http://goo.gl/GzBKEE

再考: GoF デザインパターン - Qiita
http://goo.gl/uWcVhD

DIの伝播について書いてあるけどサービス層とレポジトリがどういうことを指してるのか。

アプリケーションの構造の設計

MVCより複雑な図がある。
単純なMVCではなく、横断的関心にどう対処するかと言うことだと思うけど、まだよくわかってない。

複数存在する View と、それらに対応する Controller あるいは Model の関係を祖結合として実現するには、まず、DI フレームワークの利用が考えられます。大規模な開発では、この方法が一般的でしょう。この場合は、DI フレームワークが用意する仕組みを使って、M-V-C 間をつなげることになります。

DI コンテナを使用しない場合は、Model をシングルトンとして実装するという方法があります。実際に値を保持する複数のオブジェクトを管理するクラスを用意し、外部からはクラスのインターフェースを通じて必要なオブジェクトへの参照を獲得するという方法です。

本記事では扱いませんでしたが、興味を持った方のために、以下が主な DI フレームワークです。
Robotlegs
Parsley
Spring ActionScript
Potomac
Swiz

なお、動的言語とDIコンテナの関係は、興味深いトピックのひとつだと思いますが、Grailsはひとつの具体的な解を示していると思います。またこれが、Railsとの主要な差異のひとつでもあるのではないでしょうか。(規約によってオブジェクトの関係が設定される、という意味で、CoCはDIの一種(自動DI)であるともいえるのでしょうけれども、DIが完全には消え去らないところがGrailsらしいというかSpringらしいところ)。

自動DI。
DIコンテナもアンチパターンがあるといわれるような点をより使い勝手を良くしようというようなことかな?

memo

デザインパターンをどうするかを処理の文脈を機械的にパースして判別するって無理かしら。

1h5wのコンテキスト分析法によるデザインパターンの見直し
現代の。。わたしが、、。。方でxを実装する。。

どのような処理があるか分割して洗い出す(横断的関心も)
デザインパターンの特性マトリクスに照らし合わせる

実装のレベルでデザインパターンを簡単にかけるようにしておきたいけど、、どうするのがいいか。

どの範囲を想定しての処理設計か

並列・グローバル・メタレベル・ハードウェア
たとえばログセッション毎の排他処理のコストはどうなのかとか
メモリを使う処理の使いどころとか

今後の案

  • DIをもうちょっと理解してナチュラルにつかえるようになる。

  • 自分のコードや使っているフレームワークなどのリーディング・リファクタリング(主に構造)

  • フレームワークでScafforld利用時のとデザインパターン

    遷移パターンのコントロールをモジュールにするかどうか
    proccess()的なものを動的にDIにするかとか

  • いろんな王道な(?)パターンをシンプルで(自分が)再利用しやすくPHPで書いていく

  • 理解が進んだら(?)デザインの因果関係の法則を考える

関係ないmemo

  • グローバルな変数にクロージャーをいれるとかどうだろう
  • 無名関数のリテラル取得ってできるのか
  • 無名関数の再利用や合成って悪かな
  • php -a で補完機能ってどうなんだろ
  • マップライブラリを整備>クラウズ・吸い上げシステム
14
11
0

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
14
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?