制御の反転:PHPの例による実装方法

ああ、もう一つのコントロールの反転の投稿


多かれ少なかれ経験豊富なプログラマーは、それぞれの実践で「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) { //some order preparing } } class MySQLOrderRepository { public function load($orderID) { // makes query to DB to fetch order row from table } } 


一般に、このコードは完全に機能し、その責任を果たします。 これにこだわることは可能でした。 しかし、突然、顧客は注文をMySQLではなく1Cに保存するという素晴らしいアイデアを持っています。 そして、ここで問題に直面しています。完全に機能するコードを変更し、MySQLOrderRepositoryを使用する各メソッドを変更する必要があります。
さらに、OrderModelのテストは作成しませんでした...

したがって、前述のコードの次の問題を区別できます。


そして、これらすべてをどうするか?

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) { //some order preparing } } abstract class OrderRepositoryFactory { /** * @return IOrderRepository */ abstract public function build(); } class DBOrderRepositoryFactory extends OrderRepositoryFactory { public function build() { return new MySQLOrderRepository(); } } class RemoteOrderRepositoryFactory extends OrderRepositoryFactory { public function build() { return new OneCOrderRepository(); } } interface IOrderRepository { public function load($orderID); } class MySQLOrderRepository implements IOrderRepository { public function load($orderID) { // makes query to DB to fetch order row from table } } class OneCOrderRepository implements IOrderRepository { public function load($orderID) { // makes query to 1C to fetch order } } 



そのような実装は私たちに何を与えますか?
  1. リポジトリオブジェクトを作成する柔軟性が与えられています—インスタンス化されたクラスは、私たちが望むものに置き換えることができます。 たとえば、DBOrderRepositoryfactoryのMySQLOrderRepositoryはOracleOrderRepositoryに置き換えることができます。 そして、それは一箇所で行われます
  2. このための特別なクラスでオブジェクトが作成されると、コードがより明確になります。
  3. オブジェクトの作成時に実行するコードを追加することもできます。 コードは1か所でのみ追加されます


この実装では解決できない問題は何ですか?
  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) { //some order preparing } } class ServiceLocator { private $services = array(); private static $serviceLocatorInstance = null; private function __construct(){} public static function getInstance() { if(is_null(self::$serviceLocatorInstance)){ self::$serviceLocatorInstance = new ServiceLocator(); } return self::$serviceLocatorInstance; } public function loadService($name, $service) { $this->services[$name] = $service; } public function getService($name) { if(!isset($this->services[$name])){ throw new InvalidArgumentException(); } return $this->services[$name]; } } interface IOrderRepository { public function load($orderID); } class MySQLOrderRepository implements IOrderRepository { public function load($orderID) { // makes query to DB to fetch order row from table } } class OneCOrderRepository implements IOrderRepository { public function load($orderID) { // makes query to 1C to fetch order } } // somewhere at the entry point of application ServiceLocator::getInstance()->loadService('orderRepository', new MySQLOrderRepository()); 



そのような実装は私たちに何を与えますか?
  1. リポジトリオブジェクトを作成する柔軟性が与えられています。 必要なクラスを名前付きサービスにバインドできます。
  2. 構成ファイルを介してサービスを構成することが可能です
  3. テスト中、サービスをMockクラスに置き換えることができます。これにより、Service Locatorを使用して問題なくクラスをテストできます。


この実装では解決できない問題は何ですか?
一般に、Service Locatorがパターンであるかアンチパターンであるかについての議論は、すでに非常に古くてボロボロです。 私の意見では、Service Locatorの主な問題
  1. ロケーターオブジェクトはグローバルオブジェクトであるため、コードの任意の部分でアクセスできるため、コードが過剰になり、モジュールの接続性を低下させるすべての試みを無効にする可能性があります。


3.依存性注入


一般に、依存性注入とは、実装を通じてクラスに外部サービスを提供することです。
そのような方法は3つあります


セッター注入


この実装メソッドを使用すると、依存関係が導入されるクラスで、対応するsetメソッドが作成され、この依存関係が設定されます

Setterインジェクションで制御の反転を実装する
 <?php class OrderModel { /** * @var IOrderRepository */ 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) { //some order preparing } } interface IOrderRepository { public function load($orderID); } class MySQLOrderRepository implements IOrderRepository { public function load($orderID) { // makes query to DB to fetch order row from table } } class OneCOrderRepository implements IOrderRepository { public function load($orderID) { // makes query to 1C to fetch order } } $orderModel = new OrderModel(); $orderModel->setRepository(new MySQLOrderRepository()); 



コンストラクター注入


この実装メソッドでは、依存関係が埋め込まれるクラスのコンストラクターに、確立された依存関係である新しい引数が追加されます
コンストラクター注入を使用して制御の反転を実装する
 <?php class OrderModel { /** * @var IOrderRepository */ 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) { //some order preparing } } interface IOrderRepository { public function load($orderID); } class MySQLOrderRepository implements IOrderRepository { public function load($orderID) { // makes query to DB to fetch order row from table } } class OneCOrderRepository implements IOrderRepository { public function load($orderID) { // makes query to 1C to fetch order } } $orderModel = new OrderModel(new MySQLOrderRepository()); 



インターフェース注入


この依存性注入の方法は、セッター注入に非常に似ていますが、この注入方法では、依存関係が注入されるクラスは、このsetメソッドを実装するためにクラスが必要とするインターフェースから継承されます。

インターフェイスインジェクションを使用して制御の反転を実装する
 <?php class OrderModel implements IOrderRepositoryInject { /** * @var IOrderRepository */ 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) { //some order preparing } } interface IOrderRepositoryInject { public function setRepository(IOrderRepository $repository); } interface IOrderRepository { public function load($orderID); } class MySQLOrderRepository implements IOrderRepository { public function load($orderID) { // makes query to DB to fetch order row from table } } class OneCOrderRepository implements IOrderRepository { public function load($orderID) { // makes query to 1C to fetch order } } $orderModel = new OrderModel(); $orderModel->setRepository(new MySQLOrderRepository()); 



Dependency Injectionによる実装は何を提供しますか?
  1. クラスコードは、抽象化ではなく、インターフェイスのみに依存するようになりました。 特定の実装は、実行時に明確にされています。
  2. このようなクラスはテストが非常に簡単です。


この実装では解決できない問題は何ですか?
実際、依存性注入に大きな欠陥は見当たりません。 これは、クラスを他のクラスからできるだけ柔軟で独立したものにする良い方法です。 おそらくこれは過剰な抽象化につながりますが、これはすでにプログラマーによる原則の具体的な実装の問題であり、原則自体ではありません

4. IoCコンテナー


IoCコンテナーは、依存関係とその実装の管理に直接関与するコンテナーです(実際には依存性注入を実装します)

IoCコンテナは多くの最新のPHPフレームワークに存在します-Symfony 2、Yii 2、Laravel、Joomlaフレームワークにも:)
その主な目標は、登録された依存関係の実装を自動化することです。 つまり クラスコンストラクターで必要なインターフェイスを指定するだけで、このインターフェイスの特定の実装を登録するだけで、クラスに依存関係が埋め込まれます。

そのようなコンテナの動作はフレームワークによって多少異なるため、フレームワークの公式リソースへのリンクを提供します。このリンクでは、コンテナの仕組みを説明しています。

symfony 2-symfony.com/doc/current/components/dependency_injection/introduction.html
Laravel-laravel.com/docs/4.2/ioc
Yii 2-www.yiiframework.com/doc-2.0/guide-concept-di-container.html

おわりに


管理逆転のトピックは、このトピックに関する数百万回、数百の投稿、および数千のコメントで取り上げられています。 しかし、それにもかかわらず、私は人々に会い、コードを見て、このトピックはPHPではまだあまり人気がないことを理解しています。優れたフレームワーク、美しい、きれいで、読みやすく、柔軟なコードを書くことができるライブラリが存在しますが。

この記事が誰かにとって有用であり、このおかげで誰かのコードが良くなることを願っています。
あなたのコメント、提案、説明、質問を書いてください-私はうれしいです

Source: https://habr.com/ru/post/J244517/


All Articles