ホームページ >バックエンド開発 >PHPチュートリアル >依存関係の反転原理
コアポイント
各固体原理の関連性についてarbitrary意的な「ソロモン」の決定を下す場合、逆転(DIP)への依存の原則が最も過小評価されていると思います。オブジェクト指向設計の分野のコア概念のいくつかは、懸念の分離やスイッチングの実装など、最初は理解するのが困難ですが、一方、インターフェイス指向のプログラミングなど、より直感的でより明確なパラダイムはより単純です。残念ながら、DIPの正式な定義は両刃の剣のような呪い/祝福に包まれています。多くの場合、人々は前述の「インターフェイス指向のプログラミング」という戒めの別の声明としての原則にデフォルトになるため、プログラマーはそれを無視します。
一見すると、上記の声明は自明のようです。現在、具体的な実装に強い依存に基づいて構築されたシステムが悪いデザインの悪い前兆であることに同意しないことを考えると、いくつかの抽象化を切り替えることは完全に合理的です。したがって、これにより、DIPの主な焦点はインターフェイス指向のプログラミングに関するものだと考えて、出発点に戻ります。実際、この原則の要件を満たす場合、実装からのインターフェイスをデカップリングすることは、半仕上げの方法にすぎません。不足している部分は、実際の反転プロセスを実装することです。もちろん、疑問が生じます:反転とは何ですか?従来、システムは常に高レベルのコンポーネント(クラスであろうとプロセスルーチンであろうと)を低レベルコンポーネントに依存するように設計されています(詳細)。たとえば、ロギングモジュールは、一連の特定のロガー(実際に情報をシステムに記録する)に強い依存関係を持っている場合があります。したがって、ロガーのプロトコルが変更されるたびに、このスキームは、プロトコルが抽象化されていても、上層層への副作用が騒々しくなります。ただし、DIPの実装は、ロギングモジュールにプロトコルを持たせることにより、これらのリップルをある程度緩和するのに役立ち、全体的な依存関係を反転させます。反転後、ロガーはプロトコルを忠実に順守する必要があるため、将来の変更がある場合は、それに応じて変更し、プロトコルの変動に適応する必要があります。要するに、これは、標準的なインターフェイスのみに依存するよりも、DIPが舞台裏で少し複雑であることを示しています - デカップリングを実装しています。はい、それは抽象化に依存する高レベルのモジュールと低レベルの両方のモジュールを抽象化に依存するようにすることについて説明しますが、同時に高レベルのモジュールにはこれらの抽象化が必要です。ご想像のとおり、DIPが実際にカバーするものを理解するのに役立つ可能性のある1つの方法は、いくつかの実用的なコード例を使用しています。したがって、この記事では、PHPアプリケーションを開発する際にこの強固な原則を活用する方法を学ぶことができるように、いくつかの例を設定します。
単純なストレージモジュール(DIPで欠落している「I」)を開発します
多くの開発者、特にオブジェクト指向のPHPを嫌う開発者は、ディップやその他の堅実な原則を厳格なドグマと見なす傾向があり、言語に固有のプラグマティズムに対抗します。このアイデアは、原則の本当の利点を示す実用的なPHPの例を野生で見つけることが困難であるため、理解できます。私は啓発されたプログラマーとして自分自身を宣伝しようとはしていません(そのスーツはうまくいきません)が、良い目標のために一生懸命働き、実用的な観点から実際のユースケースにDIPを実装する方法を示すことは依然として役立ちます。まず、シンプルなファイルストレージモジュールの実装を検討します。このモジュールは、指定されたターゲットファイルからデータの読み取りと書き込みを担当します。非常に単純化されたレベルでは、質問のモジュールは次のように書くことができます:
<code class="language-php"><?php namespace LibraryEncoderStrategy; class Serializer implements Serializable { protected $unserializeCallback; public function __construct($unserializeCallback = false) { $this->unserializeCallback = (boolean) $unserializeCallback; } public function getUnserializeCallback() { return $this->unserializeCallback; } public function serialize($data) { if (is_resource($data)) { throw new InvalidArgumentException( "PHP resources are not serializable."); } if (($data = serialize($data)) === false) { throw new RuntimeException( "Unable to serialize the supplied data."); } return $data; } public function unserialize($data) { if (!is_string($data) || empty($data)) { throw new InvalidArgumentException( "The data to be decoded must be a non-empty string."); } if ($this->unserializeCallback) { $callback = ini_get("unserialize_callback_func"); if (!function_exists($callback)) { throw new BadFunctionCallException( "The php.ini unserialize callback function is invalid."); } } if (($data = @unserialize($data)) === false) { throw new RuntimeException( "Unable to unserialize the supplied data."); } return $data; } }</code>
<code class="language-php"><?php namespace LibraryFile; class FileStorage { const DEFAULT_STORAGE_FILE = "default.dat"; protected $serializer; protected $file; public function __construct(Serializable $serializer, $file = self::DEFAULT_STORAGE_FILE) { $this->serializer = $serializer; $this->setFile($file); } public function getSerializer() { return $this->serializer; } public function setFile($file) { if (!is_file($file) || !is_readable($file)) { throw new InvalidArgumentException( "The supplied file is not readable or writable."); } $this->file = $file; return $this; } public function getFile() { return $this->file; } public function resetFile() { $this->file = self::DEFAULT_STORAGE_FILE; return $this; } public function write($data) { try { return file_put_contents($this->file, $this->serializer->serialize($data)); } catch (Exception $e) { throw new Exception($e->getMessage()); } } public function read() { try { return $this->serializer->unserialize( @file_get_contents($this->file)); } catch (Exception $e) { throw new Exception($e->getMessage()); } } }</code>
このモジュールは、いくつかの基本的なコンポーネントのみで構成されるかなり単純な構造です。ファーストクラスはファイルシステムからデータを読み取り、書き込みます。2番目のクラスは、内部でデータの保存可能な表現を生成するための単純なPHPシリアナーです。これらのサンプルコンポーネントは、単独でビジネスをうまく機能させ、このように接続して同期して作業することができます。
<code class="language-php"><?php use LibraryLoaderAutoloader, LibraryEncoderStrategySerializer, LibraryFileFileStorage; require_once __DIR__ . "/Library/Loader/Autoloader.php"; $autoloader = new Autoloader; $autoloader->register(); $fileStorage = new FileStorage(new Serializer); $fileStorage->write(new stdClass()); print_r($fileStorage->read()); $fileStorage->write(array("This", "is", "a", "sample", "array")); print_r($fileStorage->read()); $fileStorage->write("This is a sample string."); echo $fileStorage->read();</code>一見すると、モジュールの機能により、ファイルシステムからさまざまなデータを簡単に保存して取得できるようにするため、モジュールはかなりまともな動作を示します。さらに、Filestorageクラスは、コンストラクターにシリアル化可能なインターフェイスを注入するため、剛性のあるコンクリートの実装ではなく、抽象化によって提供される柔軟性に依存しています。これらの利点があるため、このモジュールの問題は何ですか?多くの場合、表面的な第一印象はトリッキーで曖昧な場合があります。よく見ると、FileStorageは実際にシリアナーに依存しているだけでなく、この厳しい依存関係のために、ターゲットファイルからデータを保存および抽出することは、PHPを使用したネイティブのシリアル化メカニズムに限定されます。データをXMLまたはJSONとして外部サービスに渡す必要がある場合はどうなりますか?適切に設計されたモジュールは再利用できなくなりました。悲しいが本物!この状況はいくつかの興味深い質問を提起します。何よりもまず、Filestorageは、相互運用可能なプロトコルが既に実装から分離されている場合でも、低レベルのシリアル化剤に強い依存を示しています。第二に、問題のプロトコルによって明らかにされた普遍性のレベルは非常に限られており、あるシリアナーを別のシリアルに交換することに制限されています。この場合、抽象化に依存することは幻想的な認識であり、DIPによって奨励される真の反転プロセスは実装されません。ファイルモジュールの一部をリファクタリングして、DIP要件に忠実に準拠することができます。そうすることで、Filestorageクラスはファイルデータを保存および抽出するために使用されるプロトコルの所有権を獲得し、低レベルのシリアル化剤への依存関係を取り除き、実行時に複数のストレージポリシーを切り替えることができます。そうすることで、実際には無料で多くの柔軟性が得られます。それでは、先に進み、ファイルストレージモジュールを真にDIPに準拠した構造に変換する方法を見てみましょう。
プロトコルの所有権とデカップリングインターフェイスと実装(DIPの完全な使用の維持)
多くの選択肢はありませんが、プロトコルの抽象化を維持しながら、Filestorageクラスとその低レベルの共同作業者との間にプロトコルの所有権を効果的に逆転させる方法がまだあります。ただし、PHPネームスペースの自然なカプセル化に依存しているため、非常に直感的な方法があります。このややとらえどころのない概念を具体的なコードに変換するために、モジュールに最初に変更する必要があるのは、PHPシリアル化以外の形式で簡単に操作できるように、ファイルデータを保存および取得するための緩いプロトコルを定義することです。以下に示すように合理化された孤立したインターフェイスは、優雅に単純に仕事をすることができます:
<code class="language-php"><?php namespace LibraryEncoderStrategy; class Serializer implements Serializable { protected $unserializeCallback; public function __construct($unserializeCallback = false) { $this->unserializeCallback = (boolean) $unserializeCallback; } public function getUnserializeCallback() { return $this->unserializeCallback; } public function serialize($data) { if (is_resource($data)) { throw new InvalidArgumentException( "PHP resources are not serializable."); } if (($data = serialize($data)) === false) { throw new RuntimeException( "Unable to serialize the supplied data."); } return $data; } public function unserialize($data) { if (!is_string($data) || empty($data)) { throw new InvalidArgumentException( "The data to be decoded must be a non-empty string."); } if ($this->unserializeCallback) { $callback = ini_get("unserialize_callback_func"); if (!function_exists($callback)) { throw new BadFunctionCallException( "The php.ini unserialize callback function is invalid."); } } if (($data = @unserialize($data)) === false) { throw new RuntimeException( "Unable to unserialize the supplied data."); } return $data; } }</code>
encoderInterfaceの存在は、ファイルモジュールの全体的な設計に大きな影響を与えるわけではないようですが、表面的に約束されているもの以上のものがあります。最初の改善は、データをエンコードおよびデコードするための非常に一般的なプロトコルの定義です。 2番目の改善は、最初の改善と同じくらい重要です。つまり、プロトコルの所有権は、インターフェイスがクラスの名前空間に存在するため、Filestorageクラスに属します。要するに、正しい名前空間を持つインターフェイスを書くだけで、まだ定義されていない低レベルのエンコーダー/デコーダーが高レベルのFileStorageに依存するようにすることができました。要するに、これはアカデミックベールの背後にある擁護をする実際の逆転プロセスです。もちろん、Filestorageクラスが以前のインターフェイスを注入する実装者に変更されていない場合、反転は不器用な中間試みになります。
<code class="language-php"><?php namespace LibraryFile; class FileStorage { const DEFAULT_STORAGE_FILE = "default.dat"; protected $serializer; protected $file; public function __construct(Serializable $serializer, $file = self::DEFAULT_STORAGE_FILE) { $this->serializer = $serializer; $this->setFile($file); } public function getSerializer() { return $this->serializer; } public function setFile($file) { if (!is_file($file) || !is_readable($file)) { throw new InvalidArgumentException( "The supplied file is not readable or writable."); } $this->file = $file; return $this; } public function getFile() { return $this->file; } public function resetFile() { $this->file = self::DEFAULT_STORAGE_FILE; return $this; } public function write($data) { try { return file_put_contents($this->file, $this->serializer->serialize($data)); } catch (Exception $e) { throw new Exception($e->getMessage()); } } public function read() { try { return $this->serializer->unserialize( @file_get_contents($this->file)); } catch (Exception $e) { throw new Exception($e->getMessage()); } } }</code>Filestorageは、コンストラクターのエンコード/デコードプロトコルの所有権を明示的に宣言しており、残っているのは、ファイルデータを複数の形式で処理できる特定の低レベルエンコーダー/デコーダーのセットを作成することです。これらのコンポーネントの最初のものは、以前に書かれたPHPシリアナーのリファクタリングの実装にすぎません。
分析Serializerの背後にあるロジックは間違いなく冗長です。それにもかかわらず、今ではゆるいエンコード/デコードの抽象化に依存しているだけでなく、抽象化の所有権が名前空間レベルで明示的に公開されていることを指摘する価値があります。繰り返しますが、さらに一歩進んで、DIPの利点を強調するために、より多くのエンコーダーの書き込みを開始できます。そうは言っても、ここに記述すべきもう1つの追加の低レベルコンポーネントがあります:
<code class="language-php"><?php use LibraryLoaderAutoloader, LibraryEncoderStrategySerializer, LibraryFileFileStorage; require_once __DIR__ . "/Library/Loader/Autoloader.php"; $autoloader = new Autoloader; $autoloader->register(); $fileStorage = new FileStorage(new Serializer); $fileStorage->write(new stdClass()); print_r($fileStorage->read()); $fileStorage->write(array("This", "is", "a", "sample", "array")); print_r($fileStorage->read()); $fileStorage->write("This is a sample string."); echo $fileStorage->read();</code>
予想どおり、余分なエンコーダの背後にある根本的なロジックは、顕著な改善とバリアントを除き、最初のPHPシリアイザーに似ていることがよくあります。さらに、これらのコンポーネントはDIP課題の要件に準拠しているため、Filestorage Namespaceで定義されているエンコード/デコードプロトコルに準拠しています。ファイルモジュールの上部レベルと下位レベルの両方のコンポーネントは抽象化に依存しており、エンコーダはファイルストレージクラスに明確な依存性を持っているため、モジュールがDIP仕様に沿って動作していると安全に主張できます。さらに、次の例は、これらのコンポーネントを組み合わせる方法を示しています。
<code class="language-php"><?php namespace LibraryFile; interface EncoderInterface { public function encode($data); public function decode($data); }</code>モジュールがクライアントコードに公開するという単純な微妙さとは別に、重要なポイントを説明し、DIPの述語が実際に古い「インターフェイス指向のプログラミング」パラダイムよりも実際に広くなる理由をかなり有益な方法で実証するのに非常に役立ちます。依存関係の反転を説明し、明示的に指定するため、さまざまなメカニズムを通じて実装する必要があります。 PHPの名前空間は、あまり負担なしでこれを達成するための優れた方法ですが、十分に構造化された高度に表現力のあるアプリケーションレイアウトを定義するなどの従来の方法は、同じ結果を生み出すことができます。
結論
<code class="language-php"><?php namespace LibraryFile; class FileStorage { const DEFAULT_STORAGE_FILE = "default.dat"; protected $encoder; protected $file; public function __construct(EncoderInterface $encoder, $file = self::DEFAULT_STORAGE_FILE) { $this->encoder = $encoder; $this->setFile($file); } public function getEncoder() { return $this->encoder; } public function setFile($file) { if (!is_file($file) || !is_readable($file)) { throw new InvalidArgumentException( "The supplied file is not readable or writable."); } $this->file = $file; return $this; } public function getFile() { return $this->file; } public function resetFile() { $this->file = self::DEFAULT_STORAGE_FILE; return $this; } public function write($data) { try { return file_put_contents($this->file, $this->encoder->encode($data)); } catch (Exception $e) { throw new Exception($e->getMessage()); } } public function read() { try { return $this->encoder->decode( @file_get_contents($this->file)); } catch (Exception $e) { throw new Exception($e->getMessage()); } } }</code>多くの場合、主観的な専門知識に基づいた意見はしばしば偏っており、もちろん、この記事の冒頭で私が表明した見解も例外ではありません。ただし、依存関係の抽象化の同義語として容易に誤解されるため、より複雑な堅実な対応物に対する依存関係の反転の原理を無視するというわずかな傾向があります。さらに、一部のプログラマーは直感的に反応する傾向があり、「反転」という用語を反転を制御する略語表現と考える傾向があり、2つは互いに関連していますが、これは最終的に誤った概念です。 DIPの真の意味を知っているので、それがもたらすすべての利点を必ず利用してください。
kentoh/shutterstockの写真
依存関係の反転原理に関するよくある質問
依存関係の反転原理(DIP)の主な目的は何ですか?従来のプログラマティックプログラミングとDIPはどう違うのですか?
DIPの実用的なアプリケーションの簡単な例を提供できますか?
私のコードでDIPを使用することの利点は何ですか?
DIPを実装することの欠点や課題は何ですか?
dipは堅実な略語の最後の原則ですが、他の原則と密接に関連しています。たとえば、単一の責任原則(SRP)とオープンおよびクローズ原理(OCP)の両方が、DIPの重要な側面であるデカップリングを促進します。リヒター置換原理(LSP)とインターフェイス分離原理(ISP)の両方が、DIPの中心にある抽象化を扱います。
コードにディップの適用を開始するにはどうすればよいですか?
DIPは私のコードのパフォーマンスを改善できますか?
DIPは、大規模で複雑なシステムにのみ便利ですか?
以上が依存関係の反転原理の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。