依赖倒置原则(SOLID)与封装(OOP的支柱)

最近我正在讨论依赖倒置原理,控制反转和依赖注入。 关于这个话题,我们正在辩论这些原则是否违反了OOP的支柱之一,即封装。

我对这些事情的理解是:

  • 依赖倒置原则意味着对象应该依赖于抽象而不是结核 - 这是实现控制反转模式和依赖注入的基本原理。
  • 控制反转是依赖反转原理的模式实现,其中抽象依赖关系取代了具体的依赖关系,允许在对象之外指定依赖关系的结构。
  • 依赖注入是实现控制反转并提供依赖性解析的设计模式。 当一个依赖被传递给一个依赖组件时就会发生注入。 实质上,依赖注入模式提供了一种将依赖抽象与具体实现耦合的机制。
  • 封装是高级对象所需的数据和功能被隔离开来并且不可访问的过程,因此程序员不知道对象是如何实现的。
  • 辩论以下面的说法得到了坚持:

    IoC不是OOP,因为它破坏了封装

    就我个人而言,我认为所有OOP开发人员都应该虔诚遵守依存倒置原则和控制倒置模式 - 我的观点如下:

    如果有(可能)不止一种方法来给猫皮,那么不要像只有一种。

    例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 容器会破坏OOP,因为它们使用反射,但我不相信IoC会破坏OOP,我也不相信IoC会破坏封装。 事实上,我会尽可能地说:

    控件的封装和反转相互愉快地相互重合,允许程序员仅通过依赖的结构,同时通过封装隐藏整体实现。

    问题:

  • IoC是依赖倒置原理的直接实现吗?
  • IoC总是打破封装,因此OOP?
  • IoC应该谨慎使用,虔诚还是恰当?
  • IoC和IoC容器有什么区别?

  • IoC总是打破封装,因此OOP?

    不,这些是分级相关的问题。 封装是OOP中最容易被误解的概念之一,但我认为这种关系最好通过抽象数据类型(ADT)来描述。 本质上,ADT是对数据和相关行为的一般描述。 这个描述是抽象的; 它省略了实现细节。 相反,它根据前后 条件描述ADT。

    这就是伯特兰梅耶所称的合同设计 。 您可以在面向对象的软件构造中阅读关于OOD的这个开创性描述的更多信息。

    对象通常被描述为具有行为的数据 。 这意味着没有数据的对象并不是真正的对象。 因此,您必须以某种方式将数据导入对象。

    例如,您可以通过其构造函数将数据传递给对象:

    public class Foo
    {
        private readonly int bar;
    
        public Foo(int bar)
        {
            this.bar = bar;
        }
    
        // Other members may use this.bar in various ways.
    }
    

    另一个选择是使用setter函数或属性。 我希望我们可以同意迄今为止封装不受侵犯。

    如果我们将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拥有一个虚拟成员,会发生什么?

    封装是否被破坏?

    鉴于Bar有一个虚拟成员,我们是否仍然可以表达关于Foo前后条件?

    如果Bar坚持利斯科夫换人原则(LSP),那就没有什么区别。 LSP明确指出,改变行为不能改变系统的正确性。 只要该合同满足,封装仍然完好无损。

    因此,LSP(其中依赖倒置原则是另一个的SOLID原则之一)不违反封装; 它描述了在存在多态性时保持封装的原理

    如果Bar是抽象基类,结论是否会改变? 一个界面?

    不,它不是:这些只是不同程度的多态性。 因此,我们可以将Bar重命名为IBar (以便表明它是一个接口)并将其传递到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也被称为OOD原则。 封装(即按合同设计)定义了基本规则。 SOLID描述了遵循这些规则的准则。


    IoC是依赖倒置原理的直接实现吗?

    这两者有关的方式是他们谈论抽象,但就是这样。 控制反转是:

    一种计算机程序的自定义编写部分接收来自通用可重用库(源)的控制流的设计,

    控制反转允许我们将自定义代码挂接到可重用库的管道中。 换句话说,反转控制是关于框架的。 不适用Inversion of Control的可重用库仅仅是一个库。 框架是可应用反转控制的可重用库。

    请注意,如果我们自己编写框架,我们作为开发人员只能应用控制反转; 您不能将应用程序的反转作为应用程序开发人员应用。 我们可以(也应该)应用依赖倒置原则和依赖注入模式。

    IoC总是打破封装,因此OOP?

    由于IoC只是关注框架的流水线,因此这里没有任何漏洞。 所以真正的问题是:依赖注入是否打破了封装。

    这个问题的答案是:不,事实并非如此。 它不会因为两个原因而破坏封装:

  • 由于依赖倒置原则声明我们应该针对抽象进行编程,因此消费者将无法访问已使用实现的内部结构,因此实现不会破坏封装到客户端。 实现可能在编译时甚至不可知或无法访问(因为它存在于未引用的程序集中),并且实现在这种情况下不会泄漏实现细节并破坏封装。
  • 虽然实现接受它在整个构造函数中需要的依赖关系,但这些依赖关系通常会存储在私有字段中,并且任何人都无法访问(即使消费者直接依赖于具体类型),因此它不会破坏封装。
  • IoC应该谨慎使用,虔诚还是恰当?

    同样,问题是“应该节省DIP和DI”。 在我看来,答案是:不,你应该在整个应用程序中实际使用它。 显然,你绝不应该虔诚地使用事物。 您应该应用固体原则,而DIP是这些原则的重要组成部分。 它们将使您的应用程序更加灵活,更易于维护,并且在大多数情况下,应用SOLID原则是非常合适的。

    IoC和IoC容器有什么区别?

    依赖注入是一种可以使用或不使用IoC容器的模式。 IoC容器仅仅是一个工具,可以帮助您以更方便的方式构建对象图,以防您有一个正确应用SOLID原则的应用程序。 如果您有一个不适用SOLID原则的应用程序,那么您将很难使用IoC容器。 您将很难应用依赖注入。 或者让我更广泛地说,无论如何,你将很难维护你的应用程序。 但绝不是IoC容器是必需的工具。 我正在开发和维护一个.NET的IoC容器,但我并不总是为我的所有应用程序使用容器。 对于大型BLOBAs(业务应用程序的无聊行),我经常使用容器,但对于较小的应用程序(或Windows服务),我并不总是使用容器。 但我几乎总是使用依赖注入作为模式,因为这是坚持DIP的最有效方式。

    注意:由于IoC容器可以帮助我们应用依赖注入模式,因此“IoC容器”对于这样的库来说是一个糟糕的名字。

    但是,尽管我上面提到了任何东西,请注意:

    在软件开发人员的真实世界中,有用性胜过理论[来自Robert C. Martin的敏捷原则,模式和实践]

    换句话说,即使DI会破坏封装,也无所谓,因为这些技术和模式已被证明是非常有价值的,因为它导致了非常灵活和可维护的系统。 实践胜过理论。


    总结这个问题:

    我们有能力让服务实例化它自己的依赖关系。

    然而,我们也有能力让服务简单地定义抽象,并要求应用程序了解依赖抽象,创建具体实现并将其传入。

    问题不是,“我们为什么这样做?” (因为我们知道有很多原因)。 但问题是,“选项2是否打破封装?”

    我的“务实”答案

    我认为马克是任何此类答案的最佳选择,正如他所说:不,封装不是人们的想法。

    封装隐藏了服务或抽象的实现细节。 依赖不是一个实现细节。 如果你认为服务是一个合同,并将其后续的子服务依赖关系看作是次级合同(等等),那么你真的只会得到一份巨大的附加合同。

    想象一下,我是一个来电者,我想用法律服务来起诉我的老板。 我的应用程序必须知道这样做的服务。 单凭这一点打破了理解,即了解完成我的目标所需的服务/合同是错误的。

    这里的争论是......是的,但我只想聘请律师,我不在乎他使用的是什么书籍或服务。 我会从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 }
        }
    }
    

    现在,我所知道的是我有一份合同,其中有其他合同可能有其他合同。 我对这些合同负有很好的责任,而不是他们的实施细节。 尽管我很乐意签署那些与我的要求相关的结论。 再次,我不在乎这些结论是如何工作的,只要我知道我有一份具有约束力的合同,即我们以某种确定的方式交换信息。

    链接地址: http://www.djcxy.com/p/14433.html

    上一篇: Dependency Inversion Principle (SOLID) vs Encapsulation (Pillars of OOP)

    下一篇: What is the formal definition of Inversion of Control?