TL;DR
- 当我们说代码“可用于扩展”时,并不自动意味着我们继承它或向现有接口添加新方法。继承只是“扩展”行为的一种方式。
- 当我们应用依赖倒置原则时,我们不直接依赖于其他具体类,因此如果我们需要它们做不同的事情,我们不需要更改这些实现。依赖于抽象的类是可扩展的,因为替换抽象的实现可以从现有类中获取新的行为,而无需修改它们。
(我有点倾向于删除其余部分,因为它用更多的文字表达了同样的意思。)
检查这句话可能有助于阐明这个问题:
然后在时间 T,method1 现在需要将“ExtraInfo”添加到其返回值上。
这听起来像是吹毛求疵,但从来没有一种方法needs返回任何东西。方法不像人有话要说并且需要说。 “需要”取决于方法的调用者。调用者需要该方法返回的内容。
如果呼叫者经过int example
并接收example.ToString()
,但现在需要接收example.ToString() + " ExtraInfo"
,那么改变的是调用者的需求,而不是被调用方法的需求。
如果调用者的需求发生了变化,是否就意味着所有调用者的需求都发生了变化?如果您更改该方法返回的内容以满足一个调用者的需求,其他调用者可能会受到不利影响。这就是为什么您可能会创建一些新的东西来满足特定调用者的需求,同时保持现有的方法或类不变。从这个意义上说,现有代码是“封闭的”,而同时其行为是开放扩展的。
此外,扩展现有代码并不一定意味着修改类、向接口添加方法或继承。它只是意味着它合并了现有的代码,同时提供了一些额外的东西。
让我们回到刚开始的课程。
public Class Abstraction : IAbstraction
{
public virtual string method1(int example)
{
return example.toString();
}
}
现在您需要一个包含该类的功能但执行不同操作的类。它可能看起来像这样。 (在这个例子中,它看起来有点矫枉过正,但在现实世界的例子中却并非如此。)
public class SomethingDifferent : IAbstraction
{
private readonly IAbstraction _inner;
public SomethingDifferent(IAbstraction inner)
{
_inner = inner;
}
public string method1(int example)
{
return _inner.method1 + " ExtraInfo";
}
}
在这种情况下,新类碰巧实现了相同的接口,因此现在您有了同一接口的两个实现。但它不需要。可能是这样的:
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";
}
}
所有这些示例都扩展了现有代码而不对其进行修改。在实践中,有时将现有属性和方法添加到新类中可能是有益的,但即使如此,我们也希望避免修改已经完成其工作的部分。如果我们编写具有单一职责的简单类,那么我们就不太可能发现自己把厨房水槽扔进现有的类中。
这与依赖倒置原则或依赖于抽象有什么关系?没有什么直接的,但是应用依赖倒置原则可以帮助我们应用开闭原则。
在实际情况下,我们的类所依赖的抽象应该为这些类的使用而设计。我们不只是采用其他人创建的任何接口并将其粘贴到我们的中心类中。我们正在设计满足我们需求的界面,然后调整其他类来满足这些需求。
例如,假设Abstraction
and IAbstraction
在你的类库中,我碰巧需要一些以某种方式格式化数字的东西,而你的类看起来可以满足我的需要。我不只是要注射IAbstraction
进入我的班级。我将编写一个接口来实现我想要的功能:
public interface IFormatsNumbersTheWayIWant
{
string FormatNumber(int number);
}
然后我将编写使用您的类的该接口的实现,例如:
public class YourAbstractionNumberFormatter : IFormatsNumbersTheWayIWant
{
public string FormatNumber(int number)
{
return new Abstraction().method1 + " my string";
}
}
(或者它可能取决于IAbstraction
使用构造函数注入,无论如何。)
如果我没有应用依赖倒置原则并且我直接依赖于Abstraction
然后我必须弄清楚如何改变你的班级来做什么
我需要。但是因为我依赖于为满足我的需求而创建的抽象,所以我会自动考虑如何合并类的行为,而不是更改它。一旦我这样做了,我显然不希望你的班级的行为发生意外的改变。
我还可以依赖你的界面 -IAbstraction
- 并创建我自己的实现。但创建我自己的也有助于我遵守接口隔离原则。我所依赖的接口是为我创建的,所以它不会有我不需要的东西。你的可能还有其他我不需要的东西,或者你可以稍后添加更多。
实际上,我们有时只是使用提供给我们的抽象,例如IDataReader
。但希望这是稍后我们编写具体实现细节时的事情。当涉及到应用程序的主要行为时(如果您正在执行 DDD,即“域”),最好定义我们的类将依赖的接口,然后使外部类适应它们。
最后,依赖于抽象的类也更具可扩展性,因为我们可以替换它们的依赖项 - 实际上改变(扩展)它们的行为,而无需对类本身进行任何更改。我们可以扩展它们而不是修改它们。