依存関係の反転原理

Jennifer Aniston
リリース: 2025-02-27 08:44:16
オリジナル
949 人が閲覧しました

The Dependency Inversion Principle

コアポイント

  • 依存関係の反転原理(DIP)は、具体的な実装ではなく抽象化に依存する高レベルモジュールと低レベルのモジュールの両方が確実に依存することにより、柔軟で手入れの行き届いやすいコードを作成します。
  • DIPは単なる「インターフェイス指向のプログラミング」ではなく、これらの抽象化を高レベルモジュールで使用することも含まれます。
  • DIPの実装は、ソフトウェアシステムの剛性や脆弱性などの問題を軽減し、変更により適応し、混乱を最小限に抑えることができます。
  • DIPの実際のアプリケーションは、高レベルモジュール(ファイルストレージシステムなど)がプロトコルを指定し、低レベルモジュール(シリアル化剤など)への従来の依存を覆すコード例で説明できます。
  • DIPは有益ですが、複雑さをもたらす可能性があり、徹底的な設計とアーキテクチャの必要性を強調するコードベースの過度の複雑さを避けるために抽象化を慎重に管理する必要があります。

各固体原理の関連性についてarbitrary意的な「ソロモン」の決定を下す場合、逆転(DIP)への依存の原則が最も過小評価されていると思います。オブジェクト指向設計の分野のコア概念のいくつかは、懸念の分離やスイッチングの実装など、最初は理解するのが困難ですが、一方、インターフェイス指向のプログラミングなど、より直感的でより明確なパラダイムはより単純です。残念ながら、DIPの正式な定義は両刃の剣のような呪い/祝福に包まれています。多くの場合、人々は前述の「インターフェイス指向のプログラミング」という戒めの別の声明としての原則にデフォルトになるため、プログラマーはそれを無視します。

  1. 高レベルのモジュールは、低レベルのモジュールに依存してはなりません。どちらも抽象化に依存する必要があります。
  2. 要約は詳細に依存してはなりません。詳細は抽象化に依存する必要があります。

一見すると、上記の声明は自明のようです。現在、具体的な実装に強い依存に基づいて構築されたシステムが悪いデザインの悪い前兆であることに同意しないことを考えると、いくつかの抽象化を切り替えることは完全に合理的です。したがって、これにより、DIPの主な焦点はインターフェイス指向のプログラミングに関するものだと考えて、出発点に戻ります。実際、この原則の要件を満たす場合、実装からのインターフェイスをデカップリングすることは、半仕上げの方法にすぎません。不足している部分は、実際の反転プロセスを実装することです。もちろん、疑問が生じます:反転とは何ですか?従来、システムは常に高レベルのコンポーネント(クラスであろうとプロセスルーチンであろうと)を低レベルコンポーネントに依存するように設計されています(詳細)。たとえば、ロギングモジュールは、一連の特定のロガー(実際に情報をシステムに記録する)に強い依存関係を持っている場合があります。したがって、ロガーのプロトコルが変更されるたびに、このスキームは、プロトコルが抽象化されていても、上層層への副作用が騒々しくなります。ただし、DIPの実装は、ロギングモジュールにプロトコルを持たせることにより、これらのリップルをある程度緩和するのに役立ち、全体的な依存関係を反転させます。反転後、ロガーはプロトコルを忠実に順守する必要があるため、将来の変更がある場合は、それに応じて変更し、プロトコルの変動に適応する必要があります。要するに、これは、標準的なインターフェイスのみに依存するよりも、DIPが舞台裏で少し複雑であることを示しています - デカップリングを実装しています。はい、それは抽象化に依存する高レベルのモジュールと低レベルの両方のモジュールを抽象化に依存するようにすることについて説明しますが、同時に高レベルのモジュールにはこれらの抽象化が必要です。ご想像のとおり、DIPが実際にカバーするものを理解するのに役立つ可能性のある1つの方法は、いくつかの実用的なコード例を使用しています。したがって、この記事では、PHPアプリケーションを開発する際にこの強固な原則を活用する方法を学ぶことができるように、いくつかの例を設定します。

単純なストレージモジュール(DIPで欠落している「I」)を開発します

多くの開発者、特にオブジェクト指向のPHPを嫌う開発者は、ディップやその他の堅実な原則を厳格なドグマと見なす傾向があり、言語に固有のプラグマティズムに対抗します。このアイデアは、原則の本当の利点を示す実用的なPHPの例を野生で見つけることが困難であるため、理解できます。私は啓発されたプログラマーとして自分自身を宣伝しようとはしていません(そのスーツはうまくいきません)が、良い目標のために一生懸命働き、実用的な観点から実際のユースケースにDIPを実装する方法を示すことは依然として役立ちます。まず、シンプルなファイルストレージモジュールの実装を検討します。このモジュールは、指定されたターゲットファイルからデータの読み取りと書き込みを担当します。非常に単純化されたレベルでは、質問のモジュールは次のように書くことができます:

<?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;
    }
}
ログイン後にコピー
ログイン後にコピー
<?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());
        }
    }
}
ログイン後にコピー
ログイン後にコピー

このモジュールは、いくつかの基本的なコンポーネントのみで構成されるかなり単純な構造です。ファーストクラスはファイルシステムからデータを読み取り、書き込みます。2番目のクラスは、内部でデータの保存可能な表現を生成するための単純な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();
ログイン後にコピー
ログイン後にコピー
一見すると、モジュールの機能により、ファイルシステムからさまざまなデータを簡単に保存して取得できるようにするため、モジュールはかなりまともな動作を示します。さらに、Filestorageクラスは、コンストラクターにシリアル化可能なインターフェイスを注入するため、剛性のあるコンクリートの実装ではなく、抽象化によって提供される柔軟性に依存しています。これらの利点があるため、このモジュールの問題は何ですか?多くの場合、表面的な第一印象はトリッキーで曖昧な場合があります。よく見ると、FileStorageは実際にシリアナーに依存しているだけでなく、この厳しい依存関係のために、ターゲットファイルからデータを保存および抽出することは、PHPを使用したネイティブのシリアル化メカニズムに限定されます。データをXMLまたはJSONとして外部サービスに渡す必要がある場合はどうなりますか?適切に設計されたモジュールは再利用できなくなりました。悲しいが本物!この状況はいくつかの興味深い質問を提起します。何よりもまず、Filestorageは、相互運用可能なプロトコルが既に実装から分離されている場合でも、低レベルのシリアル化剤に強い依存を示しています。第二に、問題のプロトコルによって明らかにされた普遍性のレベルは非常に限られており、あるシリアナーを別のシリアルに交換することに制限されています。この場合、抽象化に依存することは幻想的な認識であり、DIPによって奨励される真の反転プロセスは実装されません。ファイルモジュールの一部をリファクタリングして、DIP要件に忠実に準拠することができます。そうすることで、Filestorageクラスはファイルデータを保存および抽出するために使用されるプロトコルの所有権を獲得し、低レベルのシリアル化剤への依存関係を取り除き、実行時に複数のストレージポリシーを切り替えることができます。そうすることで、実際には無料で多くの柔軟性が得られます。それでは、先に進み、ファイルストレージモジュールを真にDIPに準拠した構造に変換する方法を見てみましょう。

プロトコルの所有権とデカップリングインターフェイスと実装(DIPの完全な使用の維持)

多くの選択肢はありませんが、プロトコルの抽象化を維持しながら、Filestorageクラスとその低レベルの共同作業者との間にプロトコルの所有権を効果的に逆転させる方法がまだあります。ただし、PHPネームスペースの自然なカプセル化に依存しているため、非常に直感的な方法があります。このややとらえどころのない概念を具体的なコードに変換するために、モジュールに最初に変更する必要があるのは、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;
    }
}
ログイン後にコピー
ログイン後にコピー

encoderInterfaceの存在は、ファイルモジュールの全体的な設計に大きな影響を与えるわけではないようですが、表面的に約束されているもの以上のものがあります。最初の改善は、データをエンコードおよびデコードするための非常に一般的なプロトコルの定義です。 2番目の改善は、最初の改善と同じくらい重要です。つまり、プロトコルの所有権は、インターフェイスがクラスの名前空間に存在するため、Filestorageクラスに属します。要するに、正しい名前空間を持つインターフェイスを書くだけで、まだ定義されていない低レベルのエンコーダー/デコーダーが高レベルのFileStorageに依存するようにすることができました。要するに、これはアカデミックベールの背後にある擁護をする実際の逆転プロセスです。もちろん、Filestorageクラスが以前のインターフェイスを注入する実装者に変更されていない場合、反転は不器用な中間試みになります。

<?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());
        }
    }
}
ログイン後にコピー
ログイン後にコピー
Filestorageは、コンストラクターのエンコード/デコードプロトコルの所有権を明示的に宣言しており、残っているのは、ファイルデータを複数の形式で処理できる特定の低レベルエンコーダー/デコーダーのセットを作成することです。これらのコンポーネントの最初のものは、以前に書かれたPHPシリアナーのリファクタリングの実装にすぎません。

分析Serializerの背後にあるロジックは間違いなく冗長です。それにもかかわらず、今ではゆるいエンコード/デコードの抽象化に依存しているだけでなく、抽象化の所有権が名前空間レベルで明示的に公開されていることを指摘する価値があります。繰り返しますが、さらに一歩進んで、DIPの利点を強調するために、より多くのエンコーダーの書き込みを開始できます。そうは言っても、ここに記述すべきもう1つの追加の低レベルコンポーネントがあります:
<?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();
ログイン後にコピー
ログイン後にコピー

予想どおり、余分なエンコーダの背後にある根本的なロジックは、顕著な改善とバリアントを除き、最初のPHPシリアイザーに似ていることがよくあります。さらに、これらのコンポーネントはDIP課題の要件に準拠しているため、Filestorage Namespaceで定義されているエンコード/デコードプロトコルに準拠しています。ファイルモジュールの上部レベルと下位レベルの両方のコンポーネントは抽象化に依存しており、エンコーダはファイルストレージクラスに明確な依存性を持っているため、モジュールがDIP仕様に沿って動作していると安全に主張できます。さらに、次の例は、これらのコンポーネントを組み合わせる方法を示しています。
<?php namespace LibraryFile;

interface EncoderInterface
{
    public function encode($data);
    public function decode($data);
}
ログイン後にコピー
モジュールがクライアントコードに公開するという単純な微妙さとは別に、重要なポイントを説明し、DIPの述語が実際に古い「インターフェイス指向のプログラミング」パラダイムよりも実際に広くなる理由をかなり有益な方法で実証するのに非常に役立ちます。依存関係の反転を説明し、明示的に指定するため、さまざまなメカニズムを通じて実装する必要があります。 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());
        }
    }
}
ログイン後にコピー
多くの場合、主観的な専門知識に基づいた意見はしばしば偏っており、もちろん、この記事の冒頭で私が表明した見解も例外ではありません。ただし、依存関係の抽象化の同義語として容易に誤解されるため、より複雑な堅実な対応物に対する依存関係の反転の原理を無視するというわずかな傾向があります。さらに、一部のプログラマーは直感的に反応する傾向があり、「反転」という用語を反転を制御する略語表現と考える傾向があり、2つは互いに関連していますが、これは最終的に誤った概念です。 DIPの真の意味を知っているので、それがもたらすすべての利点を必ず利用してください。

kentoh/shutterstockの写真

依存関係の反転原理

に関するよくある質問

依存関係の反転原理(DIP)の主な目的は何ですか?

依存関係の反転原理(DIP)は、オブジェクト指向プログラミングの固体原理の重要な側面です。その主な目的は、ソフトウェアモジュールを切り離すことです。これは、複雑なロジックを提供する高レベルモジュールが、基本操作を提供する低レベルモジュールから分離されていることを意味します。そうすることで、低レベルのモジュールの変更は高レベルのモジュールに最小限の影響を与え、システム全体の管理と保守を容易にします。

従来のプログラマティックプログラミングとDIPはどう違うのですか?

従来のプログラマティックプログラミングには、通常、低レベルモジュールに依存する高レベルのモジュールが含まれます。これにより、1つのモジュールへの変更が他のモジュールに大きな影響を与える可能性のある剛性システムにつながる可能性があります。一方、ディップはこの依存関係を反転させます。高レベルのモジュールと低レベルのモジュールはどちらも抽象化に依存しているため、柔軟性を促進し、システムを変更により適応させます。

DIPの実用的なアプリケーションの簡単な例を提供できますか?

もちろん、ファイルからデータを読み取り、処理する簡単なプログラムの例を考えてみましょう。従来の方法では、処理モジュールはファイルの読み取りモジュールに直接依存する場合があります。ただし、DIPを使用すると、両方のモジュールが「DataReader」インターフェイスなどの抽象化に依存します。これは、処理モジュールがファイル読み取りモジュールに直接バインドされておらず、処理モジュールを変更せずに異なるデータソース(データベースやWebサービスなど)に簡単に切り替えることができることを意味します。

私のコードでDIPを使用することの利点は何ですか?

dipは、コードにいくつかの利点をもたらすことができます。デカップリングを促進するため、システムはより柔軟になり、変更が容易になります。また、依存関係を簡単にock笑またはスタブすることができるため、コードのテスト可能性も向上します。さらに、実装指向のプログラミングではなく、インターフェイス指向のプログラミングなどの優れた設計プラクティスを促進します。

DIPを実装することの欠点や課題は何ですか?

DIPには多くの利点がありますが、特に抽象化の数を管理が困難になる可能性のある大規模なシステムでは、複雑さを導入することもできます。また、インターフェイスを定義し、他のクラスを作成してそれらを実装する必要があるため、より多くのコードライティングにつながる可能性があります。ただし、これらの課題は、優れたデザインと建築の実践によって軽減できます。

DIPは他の堅実な原則と何の関係がありますか?

dipは堅実な略語の最後の原則ですが、他の原則と密接に関連しています。たとえば、単一の責任原則(SRP)とオープンおよびクローズ原理(OCP)の両方が、DIPの重要な側面であるデカップリングを促進します。リヒター置換原理(LSP)とインターフェイス分離原理(ISP)の両方が、DIPの中心にある抽象化を扱います。

dipはjava以外の言語で使用できますか?

絶対に。 DIPは通常、Javaやその他のオブジェクト指向の言語のコンテキストで説明されていますが、原則自体は言語に依存しません。インターフェイスや抽象クラスなど、抽象化をサポートする任意の言語でディップを適用できます。

コードにディップの適用を開始するにはどうすればよいですか?

良い出発点は、高レベルのモジュールが低レベルのモジュールに直接依存するコード内の領域を見つけることです。これらのモジュール間に抽象化を導入してそれらを切り離すことができるかどうかを検討してください。目標は、すべての直接的な依存関係を排除することではなく、依存関係が具体的な実装ではなく抽象化を目的としていることを確認することであることを忘れないでください。

DIPは私のコードのパフォーマンスを改善できますか?

DIPは、主にパフォーマンスではなく、コードの構造と保守性を向上させるために使用されます。ただし、コードをよりモジュール化し、理解しやすくすることで、パフォーマンスのボトルネックをより効率的に特定して解決するのに役立ちます。

DIPは、大規模で複雑なシステムにのみ便利ですか?

DIPの利点は、大規模な複雑なシステムではより明白なことがよくありますが、小規模なプロジェクトでも役立つ可能性があります。小さなコードベースであっても、デカップリングモジュールにより、コードが理解し、テストし、変更できるようになります。

以上が依存関係の反転原理の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
著者別の最新記事
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート