web-dev-qa-db-ja.com

依存関係逆転原理(SOLID)とカプセル化(OOPの柱)

私は最近依存関係逆転原理の逆転制御について議論していました)およびDependency Injection。このトピックに関連して、これらの原則がOOPの柱の1つ、つまりEncapsulationに違反しているかどうかについて議論しました。

これらについての私の理解は:

  • Dependency Inversion Principleは、オブジェクトが具体化ではなく抽象化に依存することを意味します。これは、制御パターンの反転と依存性注入が実装される基本的な原理です。
  • Inversion of Controlは、依存関係の逆転の原理のパターン実装であり、抽象的な依存関係が具体的な依存関係を置き換え、依存関係の具体化をオブジェクトの外部で指定できるようにします。
  • Dependency Injectionは、制御の反転を実装し、依存関係の解決を提供する設計パターンです。インジェクションは、依存関係が依存コンポーネントに渡されるときに発生します。本質的に、Dependency Injectionパターンは、依存関係の抽象化を具体的な実装と結び付けるメカニズムを提供します。
  • カプセル化は、上位レベルのオブジェクトに必要なデータと機能が隔離されてアクセスできないプロセスであるため、プログラマはオブジェクトの実装方法を認識していません。

議論は次の声明で固執しました:

IoCはカプセル化を破壊するため、OOPではありません。

個人的には、依存関係の逆転の原則と制御の逆転のパターンは、すべてのOOP開発者が真剣に観察する必要があると思います。そして私は次の引用に従っています。

猫の皮をむく方法が(潜在的に)複数ある場合、しないでくださいは1つしかないように動作します。

例1:

_class Program {
    void Main() {
        SkinCatWithKnife skinner = new SkinCatWithKnife ();
        skinner.SkinTheCat();
    }
}
_

ここでは、カプセル化の例を示します。プログラマーはMain()を呼び出すだけで、猫にスキンが適用されますが、かみそりのように鋭い歯のセットで猫にスキンを適用したい場合はどうでしょうか。

例2:

_class Program {
    // Encapsulation
    ICatSkinner skinner;

    public Program(ICatSkinner skinner) {
        // Inversion of control
        this.skinner = skinner;
    }

    void Main() {
        this.skinner.SkinTheCat();
    }
}

... new Program(new SkinCatWithTeeth());
    // Dependency Injection
_

ここでは、プログラマが具体的な依存関係を渡すことができるように抽象(ICatSkinner)が提供されているため、依存関係の逆転の原理と制御の逆転を観察します。 ついに、猫の皮をむく方法は複数あります!

ここでの争いは、これはカプセル化を壊しますか?技術的には、.SkinTheCat();Main()メソッド呼び出し内にカプセル化されているため、プログラマーはこのメソッドの動作を認識していないため、カプセル化が壊れるとは思わないでしょう。

IoC containers break OOP彼らはリフレクションを使用しているので、私はIoCがOOPを壊すと確信していませんカプセル化。実際、私は次のように言っています。

カプセル化と制御の反転は互いにうまく一致しているため、プログラマーは依存関係の具体例のみを渡すことができ、カプセル化によって実装全体を隠すことができます。

質問:

  • IoCは依存関係逆転原理の直接実装ですか?
  • IoCは常にカプセル化を中断するため、OOPは中断されますか?
  • IoCは控えめに、宗教的に、または適切に使用する必要がありますか?
  • IoCとIoCコンテナーの違いは何ですか?
43
Matthew Layton

IoCは常にカプセル化を中断するため、OOPは中断されますか?

いいえ、これらは階層的に関連する問題です。カプセル化はOOPで最も誤解されている概念の1つですが、関係は抽象データ型(ADT)を介して最も適切に記述されていると思います。基本的に、ADTはデータとそれに関連する動作の一般的な説明です。この説明は抽象的です。実装の詳細は省略されています。代わりに、pre-およびpost-conditions

これは、Bertrand Meyerが契約による設計と呼んでいるものです。このOODの独創的な説明の詳細については、 Object-Oriented Software Construction を参照してください。

オブジェクトは、data with behaviourとして記述されることがよくあります。これは、データなしのオブジェクトは実際にはオブジェクトではないことを意味します。したがって、何らかの方法でデータをオブジェクトに取り込む必要があります。

たとえば、コンストラクタを介してオブジェクトにデータを渡すことができます。

public class Foo
{
    private readonly int bar;

    public Foo(int bar)
    {
        this.bar = bar;
    }

    // Other members may use this.bar in various ways.
}

別のオプションは、セッター関数またはプロパティを使用することです。これまでのところ、カプセル化に違反していないことに同意できれば幸いです。

barを整数から別の具象クラスに変更するとどうなりますか?

public class Foo
{
    private readonly Bar bar;

    public Foo(Bar bar)
    {
        this.bar = bar;
    }

    // Other members may use this.bar in various ways.
}

以前との唯一の違いは、barがプリミティブではなくオブジェクトになったことです。ただし、オブジェクト指向の設計では整数もオブジェクトであるため、これは誤った区別です。プリミティブ(文字列、整数、ブールなど)と「実際の」オブジェクトとの間に実際の違いがあるのは、さまざまなプログラミング言語(Java、C#など)でのパフォーマンスの最適化のためだけです。 OODの観点から、それらはすべて同じです。文字列にも動作があります。文字列をすべて大文字にしたり、逆にしたりできます。

Barが非仮想メンバーのみを持つシールされた/最終的な具象クラスである場合、カプセル化に違反していますか?

barは、整数のように動作するデータだけですが、それ以外は違いはありません。これまでのところ、カプセル化は違反されていません。

Barに単一の仮想メンバーを許可するとどうなりますか?

カプセル化はそれによって壊れていますか?

Fooに単一の仮想メンバーがある場合、Barに関する事前条件と事後条件を引き続き表現できますか?

BarLiskov Substitution Principle (LSP)に準拠している場合、違いはありません。 LSPは、動作を変更してもシステムの正確さを変更してはならないことを明示的に述べています。 contractが満たされている限り、カプセル化はそのままです。

したがって、LSP( SOLID原則 の1つであり、その 依存関係逆転原理 もその1つです)は、カプセル化に違反しません。 ポリモーフィズムの存在下でmaintainカプセル化の原理を説明します。

Barが抽象基本クラスの場合、結論は変わりますか?インターフェース?

いいえ、そうではありません。これらは、ポリモーフィズムの程度が異なるだけです。したがって、BarIBarに名前変更して(インターフェースであることを示唆するため)、データとしてFooに渡すことができます。

public class Foo
{
    private readonly IBar bar;

    public Foo(IBar bar)
    {
        this.bar = bar;
    }

    // Other members may use this.bar in various ways.
}

barは別の多態性オブジェクトであり、LSPが保持されている限り、カプセル化が保持されます。

TL; DR

SOLIDがThe Principles of OODとしても知られている理由があります。カプセル化(つまり、契約による設計)は、基本ルールを定義します。 SOLIDは、これらのルールに従うためのガイドラインを示しています。

43
Mark Seemann

IoCは依存関係逆転原理の直接実装ですか?

2つは抽象化について話す方法で関連していますが、それはそれについてです。制御の反転は:

コンピュータープログラムのカスタム記述部分が汎用の再利用可能なライブラリから制御のフローを受け取る設計( ソース

コントロールの反転により、カスタムコードを再利用可能なライブラリのパイプラインにフックできます。つまり、反転制御はフレームワークに関するものです。制御の反転を適用しない再利用可能なライブラリは、単なるライブラリです。フレームワークは、制御の反転を実行する再利用可能なライブラリです。

開発者として私たち自身がフレームワークを記述している場合にのみ、制御の反転を適用できることに注意してください。アプリケーション開発者として制御の反転を適用することはできません。ただし、依存関係の逆転の原理と依存関係の注入パターンは適用できます(適用する必要があります)。

IoCは常にカプセル化を中断するため、OOPは中断されますか?

IoCはフレームワークのパイプラインにフックするだけなので、ここでリークしているものはありません。したがって、本当の問題は、依存性注入がカプセル化を解除するかどうかです。

その質問への答えは次のとおりです。いいえ、ありません。次の2つの理由により、カプセル化が壊れることはありません。

  • 依存関係の逆転の原則では、抽象化に対してプログラムする必要があると述べているため、コンシューマは使用されている実装の内部にアクセスできず、したがって、その実装はクライアントへのカプセル化を壊しません。 (参照されていないアセンブリに存在するため)コンパイル時に実装が不明またはアクセスできない場合もあり、その場合、実装は実装の詳細をリークせず、カプセル化を解除できません。
  • 実装はコンストラクタ全体で必要な依存関係を受け入れますが、これらの依存関係は通常プライベートフィールドに格納され、誰もがアクセスできないため(コンシューマが直接具象型に依存している場合でも)、カプセル化が解除されることはありません。

IoCは控えめに、宗教的に、または適切に使用する必要がありますか?

繰り返しになりますが、問題は「DIPとDIを控えめに使用すべきか」です。私の意見では、答えは次のとおりです。いいえ、実際にはアプリケーション全体で使用する必要があります。明らかに、あなたは物事を宗教的に適用すべきではありません。 SOLIDの原則を適用する必要があります。DIPはこれらの原則の重要な部分です。これにより、アプリケーションの柔軟性と保守性が向上し、ほとんどのシナリオではSOLIDの原則を適用することが非常に適切です。

IoCとIoCコンテナーの違いは何ですか?

依存性注入は、IoCコンテナの有無にかかわらず適用できるパターンです。 IoCコンテナは、SOLIDの原則を正しく適用するアプリケーションがある場合に、より便利な方法でオブジェクトグラフを構築するのに役立つツールにすぎません。 SOLIDの原則を適用しないアプリケーションがある場合、IoCコンテナーを使用するのは困難です。 Dependency Injectionを適用するのは難しいでしょう。または、もっと大まかに言えば、とにかくアプリケーションを維持するのは難しいでしょう。しかし、方法ではありませんIoCコンテナは必須のツールです。私は.NET用のIoCコンテナーを開発および保守していますが、アプリケーションのallに常にコンテナーを使用するわけではありません。大きなBLOBA(退屈な基幹業務アプリケーション)の場合はコンテナーをよく使用しますが、小さなアプリ(またはWindowsサービス)の場合は常にコンテナーを使用するわけではありません。しかし、私はdoほとんどの場合、依存性注入をパターンとして使用します。これは、これがDIPに準拠する最も効果的な方法だからです。

注:IoCコンテナーは依存性注入パターンの適用に役立つため、「IoCコンテナー」はそのようなライブラリのひどい名前です。

しかし、私が上で言ったことにもかかわらず、次のことに注意してください:

ソフトウェア開発者の現実の世界では、有用性が理論に勝っています[Robert C. Martinの アジャイルの原則、パターン、および実践 から]

言い換えると、DIがカプセル化を解除しても、これらの技術とパターンは非常に貴重であることが証明されているため、非常に柔軟で保守可能なシステムが実現します。練習は理論を切り捨てます。

34
Steven

私の理解によれば、私はあなたの質問に答えようとします:

  • IoCは依存関係逆転原理の直接実装ですか?

    ioCをDIPの直接実装としてラベル付けすることはできません。DIPは、下位レベルのモジュールの具体化ではなく、抽象化に応じて上位レベルのモジュールを作成することに焦点を当てているためです。しかし、むしろIoCはDependency Injectionの実装です。

  • IoCは常にカプセル化を中断するため、OOPは中断されますか?

    IoCのメカニズムがカプセル化に違反することはないと思います。しかし、システムを密結合にすることができます。

  • IoCは控えめに、宗教的に、または適切に使用する必要がありますか?

    IoCは、ブリッジパターンのような多くのパターンでとして使用できます。そこでは、抽象化からコンクリートを分離することでコードが改善されます。したがって、DIPを実現するために使用できます。

  • IoCとIoCコンテナーの違いは何ですか?

    IoCはDependency Inversionのメカニズムですが、コンテナはIoCを使用するものです。

1
Abdul Rahman K

質問の要約:

サービスには、独自の依存関係をインスタンス化する機能があります。

しかし、サービスには抽象化を単純に定義し、アプリケーションに依存する抽象化について知ってもらい、具体的な実装を作成してそれらを渡す機能も必要です。

そして、問題は「なぜそれを行うのですか?」です(理由の膨大なリストがあることを知っているため)。しかし、問題は、「オプション2はカプセル化を解除しないのですか?」です。

私の「プラグマティック」な答え

私はマークがそのような答えには最善の策だと思います、そして彼が言うように:いいえ、カプセル化は人々がそう思っているものではありません。

カプセル化は、サービスまたは抽象化の実装の詳細を隠しています。依存関係は実装の詳細ではありません。サービスを契約と見なし、その後のサブサービスの依存関係をサブコントラクト(チェーンなど)と見なすと、実際には、追加の1つの巨大な契約になってしまいます。

私が電話をかけていて、上司を訴えるために法的サービスを利用したいとします。私のアプリケーションは、そうするサービスについて知っているでなければなりません。それだけでも、私の目標を達成するために必要なサービス/契約について知ることは誤りであるという理論を破ります。

そこには議論があります...そうです、しかし私は弁護士を雇いたいだけです。彼がどの本やサービスを使っているかは気にしません。私はinterwebzからランダムな奇妙なものを手に入れ、彼の実装の詳細を気にしません...

sub main() {
    LegalService legalService = new LegalService();

    legalService.SueMyManagerForBeingMean();
}

public class LegalService {
    public void SueMyManagerForBeingMean(){
        // Implementation Details.
    }
}

しかし、結局のところ、仕事を終わらせるには、職場の法律を理解するなど、他のサービスが必要です。そして、結局のところ...弁護士が私の名前で署名している契約や、彼が私のお金を盗むために行っているその他のことにも非常に興味があります。たとえば...なぜこのインターネット弁護士は韓国を拠点にしているのですか?それはどのように私を助けてくれますか?これは実装の詳細ではなく、私が管理して満足している要件の依存チェーンの一部です。

sub main() {
    IWorkLawService understandWorkplaceLaw = new CaliforniaWorkplaceLawService();
    //IWorkLawService understandWorkplaceLaw = new NewYorkWorkplaceLawService();
    LegalService legalService = new LegalService(understandWorkplaceLaw);

    legalService.SueMyManagerForBeingMean();
}

public interface ILegalContract {
    void SueMyManagerForBeingMean();
}

public class LegalService : ILegalContract {
    private readonly IWorkLawService _workLawService;

    public LegalService(IWorkLawService workLawService) {
        this._workLawService = workLawService;
    }

    public void SueMyManagerForBeingMean() {
        //Implementation Detail
        _workLawService.DoSomething; // { implementation detail in there too }
    }
}

今、私が知っているのは、他の契約があるかもしれない他の契約がある契約があるということだけです。私はそれらの契約に対して非常に責任があり、それらの実装の詳細については責任を負いません。私の要件に関連する具体的な契約をこれらの契約に署名できることは、非常にうれしいことです。繰り返しになりますが、私が何らかの定義された方法で情報を交換するという拘束力のある契約を結んでいることがわかっている限り、これらのコンクリーションがどのように機能するかについては気にしません。

1
Suamere

カプセル化は、オブジェクト指向プログラミングの世界における依存関係の逆転の原則と矛盾しません。たとえば、自動車の設計では、外界からカプセル化される「内部エンジン」と、簡単に交換でき、自動車の外部コンポーネントと見なされる「ホイール」があります。車にはホイールのシャフトを回転させる仕様(インターフェース)があり、ホイールコンポーネントはシャフトと相互作用するパーツを実装します。

ここでは、内部エンジンはカプセル化プロセスを表し、ホイールコンポーネントは車の設計における依存関係逆転原理(DIP)を表しています。 DIPを使用すると、基本的にモノリシックオブジェクトの構築を回避し、代わりにオブジェクトを構成可能にします。ホイールが車に組み込まれているためにホイールを交換することができない車を構築することを想像できますか。

また、依存関係の逆転の原則の詳細については、私のブログ Here を参照してください。

0

他の多くの人が他のすべてに答えたので、私は1つの質問に答えるつもりです。そして、心に留めておいてください。正解や不正解はなく、ユーザーの好みだけです。

IoCは控えめに、宗教的に、または適切に使用する必要がありますか?私の経験から、Dependency Injectionは一般的で将来変更する必要がある可能性のあるクラスでのみ使用する必要があると思います。熱心にそれを使用すると、いくつかのクラスがコンストラクタで15のインターフェースを必要とし、本当に時間がかかる可能性があります。これは、20%の開発と80%のハウスキーピングにつながる傾向があります。

誰かが車の例を挙げ、車の製作者がタイヤをどのように変更したいのかを考えました。依存性注入により、特定の実装の詳細を気にすることなくタイヤを変更できます。しかし、依存関係の注入を真剣に行う場合...次に、タイヤの構成要素へのインターフェースの構築を開始する必要があります...さて、タイヤのスレッドについてはどうでしょうか?それらのタイヤのステッチはどうですか?それらのスレッドの化学物質はどうですか?それらの化学物質の原子はどうですか?など...わかりました!おい!ある時点で「十分で十分」と言わなければならないでしょう!細かいことすべてをインターフェースに変えないでください。時間がかかりすぎる可能性があるためです。一部のクラスを自己完結させ、クラス自体にインスタンス化しても問題ありません。開発はより速く、クラスのインスタンス化は非常に簡単です。

ちょうど私の2セント。

0
Babak