依赖倒置原则(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总是打破封装,因此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)