C# 实体打开/关闭原则如何与依赖项注入和依赖项反转相适应

C# 实体打开/关闭原则如何与依赖项注入和依赖项反转相适应,c#,solid-principles,C#,Solid Principles,我开始运用坚实的原则,发现它们有点矛盾。我的问题如下: container.Register<IAbstraction, Abstraction>(); 我对依赖倒置原则的理解是类应该依赖于抽象。实际上,这意味着类应该从接口派生。到目前为止一切都很好 接下来,我对开/闭原则的理解是,在某个截止点之后,不应该更改类的内容,而应该扩展和重写。到目前为止,这对我来说是有道理的 因此,鉴于上述情况,我最终会得出如下结论: public interface IAbstraction {

我开始运用坚实的原则,发现它们有点矛盾。我的问题如下:

container.Register<IAbstraction, Abstraction>();
我对依赖倒置原则的理解是类应该依赖于抽象。实际上,这意味着类应该从接口派生。到目前为止一切都很好

接下来,我对开/闭原则的理解是,在某个截止点之后,不应该更改类的内容,而应该扩展和重写。到目前为止,这对我来说是有道理的

因此,鉴于上述情况,我最终会得出如下结论:

public interface IAbstraction
{
    string method1(int example);
}

public Class Abstraction : IAbstraction
{
   public virtual string method1(int example)
   {
       return example.toString();
   }
}
然后在时间T,method1现在需要在其返回值上添加“ExtraInfo”。我不会改变当前的实现,而是创建一个扩展
抽象
的新类,并让它做我需要的事情,如下所示

public Class AbstractionV2 : Abstraction 
{
   public override string method1(int example)
   {
       return example.toString() + " ExtraInfo";
   }
}
我可以看出这样做的原因是,只有我想调用这个更新方法的代码才会调用它,其余的代码会调用旧方法

所有这些对我来说都是有意义的-我认为我的理解是正确的

但是,我也使用依赖注入(简单注入),因此我的实现从来都不是通过一个具体的类,而是通过我的DI配置,如下所示:

container.Register<IAbstraction, Abstraction>();
container.Register();
这里的问题是,在此设置下,我可以将我的DI配置更新为:

container.Register<IAbstraction, AbstractionV2>();
container.Register();
在这种情况下,所有实例现在都将调用新方法,这意味着我无法实现不更改原始方法的目标

我创建了一个新的接口
IAbstractionV2
,并在那里实现了更新的功能,这意味着接口声明的重复


我看不出有什么办法可以解决这个问题——这让我想知道依赖注入和SOLID是否兼容?还是我在这里遗漏了什么?

模块一旦被其他模块引用,就无法进行修改。关闭的是公共API,即接口。可以通过多态替换(在新类中实现接口并注入接口)来更改行为。你的IoC容器可以注入这个新的实现。这种多态替换的能力是“开放扩展”部分。因此,DIP和打开/关闭可以很好地协同工作

参见:“在20世纪90年代,开放/封闭原则被普遍重新定义为使用抽象接口……”

TL;博士

  • 当我们说代码“可用于扩展”时,这并不自动意味着我们从中继承或向现有接口添加新方法。继承只是“扩展”行为的一种方式
  • 当我们应用依赖倒置原则时,我们并不直接依赖于其他具体的类,因此如果我们需要它们做一些不同的事情,我们不需要更改这些实现。依赖于抽象的类是可扩展的,因为替换抽象的实现可以在不修改现有类的情况下从现有类获得新的行为
(我有点倾向于删除剩下的部分,因为它用更多的词表达了同样的意思。)


研究这句话可能有助于阐明这个问题:

然后在时间T,method1现在需要在其返回值上添加“ExtraInfo”

这听起来像是在吹毛求疵,但方法永远不需要返回任何东西。方法不像人们有话要说,需要说。“需要”取决于方法的调用方。调用方需要方法返回的内容

如果调用方正在传递
int-example
并接收
example.ToString()
,但现在它需要接收
example.ToString()+“ExtraInfo”
,那么是调用方的需要发生了变化,而不是调用方法的需要

如果呼叫者的需求发生了变化,是否意味着所有呼叫者的需求都发生了变化?如果更改方法返回的内容以满足一个调用方的需要,则其他调用方可能会受到不利影响。这就是为什么在保持现有方法或类不变的情况下,您可以创建满足某个特定调用方需要的新内容。从这个意义上讲,现有代码是“封闭的”,同时它的行为是开放的,可以扩展

此外,扩展现有代码并不一定意味着修改类、向接口添加方法或继承。它只是意味着它在提供额外内容的同时合并了现有代码

让我们回到你开始的那节课

public Class Abstraction : IAbstraction
{
     public virtual string method1(int example)
     {
         return example.toString();
     }
}
现在,您需要一个包含该类功能但做了一些不同事情的类。它可能看起来像这样。(在本例中,这看起来有些过分,但在现实世界中,情况并非如此。)

在本例中,新类恰好实现了相同的接口,因此现在您有了相同接口的两个实现。但它不需要。可能是这样的:

public class SomethingDifferent
{
     private readonly IAbstraction _inner;

     public SomethingDifferent(IAbstraction inner)
     {
         _inner = inner;
     }

     public string DoMyOwnThing(int example)
     {
         return _inner.method1 + " ExtraInfo";
     }
}
您还可以通过继承“扩展”原始类的行为:

public Class AbstractionTwo : Abstraction
{
     public overrride string method1(int example)
     {
         return base.method1(example) + " ExtraInfo";
     }
}
所有这些示例都在不修改现有代码的情况下扩展了现有代码。在实践中,有时将现有属性和方法添加到新类中可能是有益的,但即使如此,我们还是希望避免修改已经在执行其任务的部分。如果我们写的是简单的、单一责任的类,那么我们就不太可能发现自己把厨房水槽扔进现有的类中


这与依赖倒置原则有什么关系,或者取决于抽象?没有直接的,但是应用依赖倒置原则可以帮助我们应用开/闭原则

在实际的情况下,我们的类所依赖的抽象应该设计为使用这些类。我们不仅仅是将其他人创建的界面粘贴到我们的ce中
public interface IFormatsNumbersTheWayIWant
{
    string FormatNumber(int number);
}
public class YourAbstractionNumberFormatter : IFormatsNumbersTheWayIWant
{
    public string FormatNumber(int number)
    {
        return new Abstraction().method1 + " my string";
    }
}
container.Register<IAbstraction, Abstraction>();
container.Register<IAbstraction, AbstractionV2>();
var container = new WindsorContainer();
container.Register(
    Component.For<ISaysHello, SaysHelloInSpanish>().IsDefault(),
    Component.For<ISaysHello, SaysHelloInEnglish>().Named("English"),
    Component.For<ISaysSomething, SaysSomething>()
        .DependsOn(Dependency.OnComponent(typeof(ISaysHello),"English")));
var container = new Container();

container.Register<ISaysSomething, SaysSomething>();

container.RegisterConditional<ISayHello, SaysHelloInEnglish>(
    c => c.Consumer.ImplementationType == typeof(SaysSomething));

container.RegisterConditional<ISayHello, SaysHelloInSpanish>(
    c => c.Consumer.ImplementationType != typeof(SaysSomething))