IOC: 英語正式名: Inversion of Control、中国語名: Inversion of Control、Dependency Injection (DI) という別名もあります。
クラスのインスタンスが別のクラスのインスタンスからの支援を必要とする場合、従来のプログラミング プロセスでは、通常、呼び出し元が呼び出し先のインスタンスを作成します。依存関係の注入では、呼び出し先を作成する作業が呼び出し元によって行われなくなるため、制御の反転と呼ばれます。呼び出し先のインスタンスを作成する作業は IOC コンテナーによって行われ、呼び出し元に注入されます。これは依存性注入とも呼ばれます。
簡単な例:
(1) 原始社会では、社会的分業はほとんどありませんでした。斧を必要とする人(呼び出し側)は、自分(呼び出し側)のみが斧を挽くことができます。
(2) 工業社会に入り、工場が出現しました。斧はもはや一般の人が完成させるものではなく、工場で生産されるようになり、斧を必要とする人(発信者)は斧の製造工程など気にせずに工場を見つけて斧を購入します。
(3) 「オンデマンド流通」社会に入ると、斧が必要な人々は工場を探す必要がなく、家に座って「斧が必要だ」という簡単な命令を出すことができます。斧は自然に彼の目の前に現れました。
最初のケースでは、インスタンスの呼び出し元が呼び出されるインスタンスを作成します。これには、呼び出されるクラスが呼び出し元のコードに現れる必要があります。 2 つの間の疎結合は実現できません。
2 番目のケースでは、呼び出し元は呼び出し先の特定の実装プロセスを気にする必要はなく、特定の標準 (インターフェイス) を満たすインスタンスを見つけるだけで済みます。このときに呼び出されるコードは、呼び出し元と呼び出し先を分離できるインターフェイス プログラミングを指向しているため、ファクトリ パターンが広く使用されています。ただし、呼び出し元は自分でファクトリを見つける必要があり、呼び出し元は特定のファクトリに接続されています。
3 番目のケースでは、呼び出し元が自分でファクトリを見つける必要がなく、プログラムが実行されて呼び出し先が必要になると、依存関係注入コンテナーが呼び出し先のインスタンスを自動的に提供します。実際には、呼び出し元と呼び出し先の両方が依存関係注入コンテナーの管理下にあり、両者の間の依存関係は依存関係注入コンテナーによって提供されます。したがって、呼び出し元と呼び出し先の間の結合がさらに軽減され、アプリケーションの保守が容易になります。これが依存関係注入の目的です。
まず、次のようなクラスを作成します。
<?php class Di{ protected $_service = []; public function set($name, $definition) { $this->_service[$name] = $definition; } public function get($name) { if (isset($this->_service[$name])) { $definition = $this->service[$name]; } else { throw new Exception("Service '" . name . "' wasn't found in the dependency injection container"); } if (is_object($definition)) { $instance = call_user_func($definition); } return $instance; }}
これで、単純なクラスが完成しました。属性と 2 つのメソッド。 redisDB とキャッシュという 2 つのクラスがあるとします。redisDB は redis データベース操作を提供し、キャッシュはキャッシュ機能の実装を担当し、redisDB に依存します。
class redisDB{ protected $_di; protected $_options; public function __construct($options = null) { $this->_options = $options; } public function setDI($di) { $this->_di = $di; } public function find($key, $lifetime) { // code } public function save($key, $value, $lifetime) { // code } public function delete($key) { // code }}
このクラスでは、redis クエリ、保存、削除を実装するだけです。別のメソッド setDi が何を行うかについて疑問があるかもしれません。引き続き説明させていただきます。別のクラスは、現在のクラスと非常によく似た構造を持っています。
class cache{ protected $_di; protected $_options; protected $_connect; public function __construct($options = null) { $this->_options = $options; } public function setDI($di) { $this->_di = $di; } protected function _connect() { $options = $this->_options; if (isset($options['connect'])) { $service = $options['connect']; } else { $service = 'redis'; } return $this->_di->get($service); } public function get($key, $lifetime) { $connect = $this->_connect; if (!is_object($connect)) { $connect = $this->_connect() $this->_connect = $connect; } // code ... return $connect->find($key, $lifetime); } public function save($key, $value, $lifetime) { $connect = $this->_connect; if (!is_object($connect)) { $connect = $this->_connect() $this->_connect = $connect; } // code ... return $connect->save($key, $lifetime); } public function delete($key) { $connect = $this->_connect; if (!is_object($connect)) { $connect = $this->_connect() $this->_connect = $connect; } // code ... $connect->delete($key, $lifetime); }}
ここでは、redisDB とキャッシュの 2 つのコンポーネントを実装したと仮定します。具体的な使用方法については説明しません。それを使ってください。まず、コンテナに 2 つのコンポーネントを注入する必要があります:
<?php $di = new Di(); $di->set('redis', function() { return new redisDB([ 'host' => '127.0.0.1', 'port' => 6379 ]); }); $di->set('cache', function() use ($di) { $cache = new cache([ 'connect' => 'redis' ]); $cache->setDi($di); return $cache; }); // 然后在任何你想使用cache的地方 $cache = $di->get('cache'); $cache->get('key'); // 获取缓存数据 $cache->save('key', 'value', 'lifetime'); // 保存数据 $cache->delete('key'); // 删除数据
この時点では、これは少し面倒に感じるかもしれません。キャッシュと redisDB の構造は非常に似ていますが、redis を個別に分離せずにキャッシュに書き込むことができますか?しかし、考えたことはありますか。一部のデータはタイムリーではなく、量も比較的多いため、redis の使用には適していません。一部のデータの更新頻度は遅く、クエリの要件もありません。速度が速いため、ファイルに直接書き込んで保存できる場合は、ハードディスクを使用する方が適切かもしれません。または、顧客が Redis の運用と保守が少し難しいと考えているため、それを memcache に置き換えるよう求めています。 . これが分離される理由です。次に、コードの改善を続けます。
interface BackendInterface { public function find($key, $lifetime); public function save($key, $value, $lifetime); public function delete($key);}class redisDB implements BackendInterface{ public function find($key, $lifetime) { } public function save($key, $value, $lifetime) { } public function delete($key) { }}class mongoDB implements BackendInterface{ public function find($key, $lifetime) { } public function save($key, $value, $lifetime) { } public function delete($key) { }}class file implements BackendInterface{ public function find($key, $lifetime) { } public function save($key, $value, $lifetime) { } public function delete($key) { }}$di = new Di();// redis$di->set('redis', function() { return new redisDB([ 'host' => '127.0.0.1', 'port' => 6379 ]);});// mongodb$di->set('mongo', function() { return new mongoDB([ 'host' => '127.0.0.1', 'port' => 12707 ]);});// file$di->set('file', function() { return new file([ 'path' => 'path' ]);});// save at redis$di->set('fastCache', function() use ($di) { $cache = new cache([ 'connect' => 'redis' ]); $cache->setDi($di); return $cache;});// save at mongodb$di->set('cache', function() use ($di) { $cache = new cache([ 'connect' => 'mongo' ]); $cache->setDi($di); return $cache;});// save at file$di->set('slowCache', function() use ($di) { $cache = new cache([ 'connect' => 'file' ]); $cache->setDi($di); return $cache;});// 然后在任何你想使用cache的地方 $cache = $di->get('cache');
新しいインターフェース BackendInterface を追加しました。これは、redisDB、mongoDB、および file の 3 つのクラスがこのインターフェースに必要な機能を実装する必要があることを規定しています。機能をさらに強化するには、「How do you play」に従ってください。キャッシュはデータがデータベースまたはファイルにどのように保存されるかを気にする必要がないため、キャッシュ コードは変更されていないように見えます。キャッシュの呼び出し元は、インターフェイスに従って対応するメソッドを実装する限り、キャッシュがどのように実装されるかを気にする必要はありません。インターフェースについて合意し、それを個別に実装するだけで、複数人でのコラボレーションからより多くのメリットが得られます。
とても簡単そうに見えますが、これが依存性注入の魅力です。
上記のコードは、完璧だと思われるまで改良を続けることができます。たとえば、redis サービスはリクエスト内で複数回呼び出され、各呼び出しが再作成されるため、パフォーマンスが低下します。 DI コンテナを展開してパラメータまたはメソッドを追加するだけです。それはあなた次第です。
class Di{ protected $_service = []; protected $_sharedService = []; public function set($name, $definition, $shared = false) { if ($shared) { $this->_sharedService[$name] = $definition; } else { $this->_service[$name] = $definition; } } public function get($name) { if (isset($this->_service[$name])) { $definition = $this->service[$name]; } else if ($this->_sharedService[$name]) { $definition = $this->_sharedService[$name]; } else { throw new Exception("Service '" . name . "' wasn't found in the dependency injection container"); } ... }
このように、1 つのリクエストでサービスが複数回呼び出される場合は、shared 属性を true に設定して不要な無駄を減らすことができます。注入するたびに setDi するのは少し面倒で、自動的に setDi させたい場合は、次のようにすることができます:
interface DiAwareInterface{ public function setDI($di); public function getDI();}class Di{ protected $service; public function set($name, $definition) { $this->service[$name] = $definition; } public function get($name) { ... if (is_object($definition)) { $instance = call_user_func($definition); } // 如果实现了DiAwareInterface这个接口,自动注入 if (is_object($instance)) { if ($instance instanceof DiAwareInterface) { $instance->setDI($this); } } return $instance; }}class redisDB implements BackendInterface, DiAwareInterface{ public function find($key, $lifetime) { } public function save($key, $value, $lifetime) { } public function delete($key) { }}
その後、次のようにすることができます:
$di->set('cache', function() { return new cache([ 'connect' => 'mongo' ]);});
現在実装されている DI コンテナはまだ非常に粗雑で、複雑なインジェクションをサポートしていません。今後も改善していきます。
ただし、これらのコードを通じて、依存関係注入とは何かをすでに理解しているので、このアイデアをプロジェクトに適用したり、独自のフレームワークの開発を開始したりできます。さらに詳しく学習したい場合は、「シーン」をクリックしてください。
終わり。