ああ、もう一つのコントロールの反転の投稿
多かれ少なかれ経験豊富なプログラマーは、それぞれの実践で「Inversion of Control」というフレーズに遭遇しています。 しかし、多くの場合、適切に実装する方法はもちろんのこと、誰もがその意味を完全に理解しているわけではありません。 この記事が、コントロールの反転に慣れ始めて、多少混乱している人たちに役立つことを願っています。
したがって、Wikipediaによると、Inversion of Controlは、次の2つの原則に基づいて、コンピュータープログラムの接続性を減らすために使用されるオブジェクト指向プログラミングの原則です。
- 最上位モジュールは、下位モジュールに依存するべきではありません。 それらも他も、抽象化に依存すべきです。
- 抽象化は詳細に依存すべきではありません。 詳細は抽象化に依存する必要があります。
つまり、モジュールのすべての依存関係は、特定の実装ではなく、これらのモジュールの抽象化に基づいている必要があると言えます。
例を考えてみましょう。
OrderModelとMySQLOrderRepositoryの2つのクラスがあるとします。 OrderModelはMySQLOrderRepositoryを呼び出して、MySQLストレージからデータを取得します。 明らかに、高レベルのモジュール(OrderModel)は、比較的低レベルのMySQLOrderRepositoryに依存しています。
不良コードの例を以下に示します。
<?php class OrderModel { public function getOrder($orderID) { $orderRepository = new MySQLOrderRepository(); $order = $orderRepository->load($orderID); return $this->prepareOrder($order); } private function prepareOrder($order) {
一般に、このコードは完全に機能し、その責任を果たします。 これにこだわることは可能でした。 しかし、突然、顧客は注文をMySQLではなく1Cに保存するという素晴らしいアイデアを持っています。 そして、ここで問題に直面しています。完全に機能するコードを変更し、MySQLOrderRepositoryを使用する各メソッドを変更する必要があります。
さらに、OrderModelのテストは作成しませんでした...
したがって、前述のコードの次の問題を区別できます。
- そのようなコードは十分にテストされていません。 2つのモジュールがしっかりと接続されている場合、それらを個別にテストすることはできません
- そのようなコードはうまく拡張されません。 上記の例が示したように、注文ストアを変更するには、注文を処理するモデルも変更する必要がありました
そして、これらすべてをどうするか?
1.ファクトリーメソッド/アブストラクトファクトリー
制御反転を実装する最も簡単な方法の1つは、
ファクトリメソッドです(抽象ファクトリも使用できます)その本質は、newを介してクラスオブジェクトを直接インスタンス化する代わりに、オブジェクトを作成するためのインターフェイスをクライアントクラスに提供することです。 適切なデザインのこのようなインターフェイスはいつでも再定義できるため、高レベルモジュールで低レベルモジュールを使用する場合、ある程度の柔軟性が得られます。
上記の注文の例を考えてみましょう。
MySQLOrderRepositoryクラスのオブジェクトを直接インスタンス化する代わりに、どのインスタンスとどのクラスを作成するかを決定するOrderRepositoryFactoryクラスのファクトリビルドメソッドを呼び出します。
ファクトリメソッドを使用して制御の反転を実装する <?php class OrderModel { public function getOrder($orderID) { $factory = new DBOrderRepositoryFactory(); $orderRepository = $factory->build(); $order = $orderRepository->load($orderID); return $this->prepareOrder($order); } private function prepareOrder($order) {
そのような実装は私たちに何を与えますか?- リポジトリオブジェクトを作成する柔軟性が与えられています—インスタンス化されたクラスは、私たちが望むものに置き換えることができます。 たとえば、DBOrderRepositoryfactoryのMySQLOrderRepositoryはOracleOrderRepositoryに置き換えることができます。 そして、それは一箇所で行われます
- このための特別なクラスでオブジェクトが作成されると、コードがより明確になります。
- オブジェクトの作成時に実行するコードを追加することもできます。 コードは1か所でのみ追加されます
この実装では解決できない問題は何ですか?- コードは低レベルモジュールに依存しなくなりましたが、それでもファクトリクラスに依存しているため、テストは依然として多少困難になります。
2.サービスロケーター
Service Locatorパターンの背後にある基本的な考え方は、必要なすべてのサービスを取得する方法を知っているオブジェクトを持つことです。 工場との主な違いは、Service Locatorはオブジェクトを作成しないが、1つまたは別のオブジェクトを取得する方法を知っていることです。 つまり 実際には既にインスタンス化されたオブジェクトが含まれています。
Service Locatorのオブジェクトは、構成ファイルを介して直接、そしてプログラマーにとって便利な方法で直接追加できます。
Service Locatorを使用して制御の反転を実装する <?php class OrderModel { public function getOrder($orderID) { $orderRepository = ServiceLocator::getInstance()->get('orderRepository'); $order = $orderRepository->load($orderID); return $this->prepareOrder($order); } private function prepareOrder($order) {
そのような実装は私たちに何を与えますか?- リポジトリオブジェクトを作成する柔軟性が与えられています。 必要なクラスを名前付きサービスにバインドできます。
- 構成ファイルを介してサービスを構成することが可能です
- テスト中、サービスをMockクラスに置き換えることができます。これにより、Service Locatorを使用して問題なくクラスをテストできます。
この実装では解決できない問題は何ですか?一般に、Service Locatorがパターンであるかアンチパターンであるかについての議論は、すでに非常に古くてボロボロです。 私の意見では、Service Locatorの主な問題
- ロケーターオブジェクトはグローバルオブジェクトであるため、コードの任意の部分でアクセスできるため、コードが過剰になり、モジュールの接続性を低下させるすべての試みを無効にする可能性があります。
3.依存性注入
一般に、依存性注入とは、実装を通じてクラスに外部サービスを提供することです。
そのような方法は3つあります
- クラスメソッド経由(セッターインジェクション)
- コンストラクターを介して(コンストラクター注入)
- インターフェイスインジェクション経由
セッター注入
この実装メソッドを使用すると、依存関係が導入されるクラスで、対応するsetメソッドが作成され、この依存関係が設定されます
Setterインジェクションで制御の反転を実装する <?php class OrderModel { private $repository; public function getOrder($orderID) { $order = $this->repository->load($orderID); return $this->prepareOrder($order); } public function setRepository(IOrderRepository $repository) { $this->repository = $repository; } private function prepareOrder($order) {
コンストラクター注入
この実装メソッドでは、依存関係が埋め込まれるクラスのコンストラクターに、確立された依存関係である新しい引数が追加されます
コンストラクター注入を使用して制御の反転を実装する <?php class OrderModel { private $repository; public function __construct(IOrderRepository $repository) { $this->repository = $repository; } public function getOrder($orderID) { $order = $this->repository->load($orderID); return $this->prepareOrder($order); } private function prepareOrder($order) {
インターフェース注入
この依存性注入の方法は、セッター注入に非常に似ていますが、この注入方法では、依存関係が注入されるクラスは、このsetメソッドを実装するためにクラスが必要とするインターフェースから継承されます。
インターフェイスインジェクションを使用して制御の反転を実装する <?php class OrderModel implements IOrderRepositoryInject { private $repository; public function getOrder($orderID) { $order = $this->repository->load($orderID); return $this->prepareOrder($order); } public function setRepository(IOrderRepository $repository) { $this->repository = $repository; } private function prepareOrder($order) {
Dependency Injectionによる実装は何を提供しますか?- クラスコードは、抽象化ではなく、インターフェイスのみに依存するようになりました。 特定の実装は、実行時に明確にされています。
- このようなクラスはテストが非常に簡単です。
この実装では解決できない問題は何ですか?実際、依存性注入に大きな欠陥は見当たりません。 これは、クラスを他のクラスからできるだけ柔軟で独立したものにする良い方法です。 おそらくこれは過剰な抽象化につながりますが、これはすでにプログラマーによる原則の具体的な実装の問題であり、原則自体ではありません
4. IoCコンテナー
IoCコンテナーは、依存関係とその実装の管理に直接関与するコンテナーです(実際には依存性注入を実装します)
IoCコンテナは多くの最新のPHPフレームワークに存在します-Symfony 2、Yii 2、Laravel、Joomlaフレームワークにも:)
その主な目標は、登録された依存関係の実装を自動化することです。 つまり クラスコンストラクターで必要なインターフェイスを指定するだけで、このインターフェイスの特定の実装を登録するだけで、クラスに依存関係が埋め込まれます。
そのようなコンテナの動作はフレームワークによって多少異なるため、フレームワークの公式リソースへのリンクを提供します。このリンクでは、コンテナの仕組みを説明しています。
symfony
2-symfony.com/doc/current/components/dependency_injection/introduction.htmlLaravel-laravel.com/docs/4.2/iocYii
2-www.yiiframework.com/doc-2.0/guide-concept-di-container.htmlおわりに
管理逆転のトピックは、このトピックに関する数百万回、数百の投稿、および数千のコメントで取り上げられています。 しかし、それにもかかわらず、私は人々に会い、コードを見て、このトピックはPHPではまだあまり人気がないことを理解しています。優れたフレームワーク、美しい、きれいで、読みやすく、柔軟なコードを書くことができるライブラリが存在しますが。
この記事が誰かにとって有用であり、このおかげで誰かのコードが良くなることを願っています。
あなたのコメント、提案、説明、質問を書いてください-私はうれしいです