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

ser*_*0ne 43 oop encapsulation design-patterns inversion-of-control solid-principles

我最近讨论了依赖性倒置原则,控制反转依赖注入.关于这个主题,我们一直在争论这些原则是否违反了OOP的一个支柱,即封装.

我对这些事情的理解是:

  • 依赖倒置原则意味着,对象应该依赖于抽象,而不是结核-这是在其控制模式和依赖注入的反转实现的基本原则.
  • 控制反转是依赖性反转原则的模式实现,其中抽象依赖性替换具体的依赖性,允许在对象之外指定依赖性的具体化.
  • 依赖注入是一种实现控制反转并提供依赖性解析的设计模式.将依赖项传递给依赖组件时会发生注入.实质上,依赖注入模式提供了一种将依赖抽象与具体实现耦合的机制.
  • 封装是一个过程,通过这个过程,更高级别对象所需的数据和功能被隔离并且不可访问,因此,程序员不知道如何实现对象.

辩论与以下声明达成了共识:

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

就个人而言,我认为所有OOP开发人员应该虔诚地遵守依赖倒置原则和控制反转模式 - 我遵循以下引用:

如果有(可能)不止一种方法给猫皮肤,那么就不要 表现得像只有一只猫.

例1:

class Program {
    void Main() {
        SkinCatWithKnife skinner = new SkinCatWithKnife ();
        skinner.SkinTheCat();
    }
}
Run Code Online (Sandbox Code Playgroud)

在这里,我们看到封装的一个例子.程序员只需要打电话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
Run Code Online (Sandbox Code Playgroud)

在这里,我们观察依赖性反转原理和控制反转,因为提供了abstract(ICatSkinner)以允许程序员传递具体的依赖性.最后,皮肤猫的方法不止一种!

这里的争吵是; 这会破坏封装吗?从技术上讲,人们可能会认为它.SkinTheCat();仍然被封装在Main()方法调用中,所以程序员不知道这个方法的行为,所以我不认为这打破了封装.

深入研究一下,我认为IoC 容器会破坏OOP,因为它们使用反射,但我不相信IoC会破坏OOP,我也不相信IoC会破坏封装.事实上,我会说:

封装和控制反转很愉快地相互重合,允许程序员只传入依赖的结构,同时通过封装隐藏整个实现.

问题:

  • IoC是否是直接实现依赖倒置原则?
  • IoC是否总是打破封装,因此是OOP?
  • IoC应该谨慎,宗教或适当地使用吗?
  • IoC和IoC容器有什么区别?

Mar*_*ann 43

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

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

这就是Bertrand Meyer所说的合同设计.您可以在面向对象的软件构建中阅读有关OOD的这一开创性描述的更多信息.

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

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

public class Foo
{
    private readonly int bar;

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

    // Other members may use this.bar in various ways.
}
Run Code Online (Sandbox Code Playgroud)

另一种选择是使用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.
}
Run Code Online (Sandbox Code Playgroud)

与之前相比的唯一区别bar是现在是一个对象,而不是一个原始对象.然而,这是一个错误的区别,因为在面向对象的设计中,整数也是一个对象.这只是因为各种编程语言(Java,C#等)中的性能优化,原语(字符串,整数,bool等)和'真实'对象之间存在实际差异.从OOD的角度来看,它们都是一样的.字符串也有行为:你可以把它们变成全大写,反转它们等等.

如果Bar密封/最终,具有非虚拟成员的具体类,是否违反了封装?

bar只是行为的数据,就像一个整数,但除此之外,没有区别.到目前为止,没有违反封装.

如果我们允许Bar拥有一个虚拟成员会怎样?

封装破坏了吗?

我们是否还可以表达前后条件Foo,因为它Bar有一个虚拟成员?

如果Bar遵守Liskov替代原则(LSP),那就不会有所作为.LSP明确指出改变行为不得改变系统的正确性.只要合同得到履行,封装仍然完好无损.

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

如果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.
}
Run Code Online (Sandbox Code Playgroud)

bar 只是另一个多态对象,只要LSP成立,封装就成立了.

TL; DR

SOLID也被称为OOD原理.封装(即按合同设计)定义了基本规则.SOLID描述了遵循这些规则的准则.


Ste*_*ven 34

IoC是否是直接实现依赖倒置原则?

这两者是相关的,他们谈论抽象,但这是关于它.控制反转是:

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

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

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

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

由于IoC只是挂钩到框架的管道,所以没有任何东西在这里泄漏.所以真正的问题是:依赖注入是否打破了封装.

这个问题的答案是:不,它没有.它没有破坏封装,原因有两个:

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

IoC应该谨慎,宗教或适当地使用吗?

同样,问题是"应该谨慎使用DIP和DI".在我看来,答案是:不,你应该在整个应用程序中使用它.显然,你不应该虔诚地申请.您应该应用SOLID原则,DIP是这些原则的重要组成部分.它们将使您的应用程序更灵活,更易于维护,在大多数情况下,应用SOLID原则非常合适.

IoC和IoC容器有什么区别?

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

注意:由于IoC容器帮助我们应用依赖注入模式,因此"IoC容器"对于此类库来说是一个糟糕的名称.

但是,尽管我上面说过,请注意:

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

换句话说,即使DI会破坏封装,也无关紧要,因为这些技术和模式已被证明非常有价值,因为它可以产生非常灵活和可维护的系统.实践胜过理论.