public class BubbleSorter
{
public void Sort(int[] data)
{
int n = data.Length;
for (int i = 0; i < n - 1; i++)
for (int j = 0; j < n - i - 1; j++)
if (data[j] > data[j + 1])
{
int temp = data[j];
data[j] = data[j + 1];
data[j + 1] = temp;
}
}
}
public class BubbleSorter<T>
{
IComparer<T> _comparer;
public BubbleSorter(IComparer<T> comparer)
{
_comparer = comparer;
}
public void Sort(T[] data)
{
int n = data.Length;
for (int i = 0; i < n - 1; i++)
for (int j = 0; j < n - i - 1; j++)
if (_comparer.Compare(data[j], data[j + 1]) > 0)
{
var temp = data[j];
data[j] = data[j + 1];
data[j + 1] = temp;
}
}
}
所以原理就是
所以,很明显我们在谈论:
它们都必须对扩展开放,对修改关闭。听起来不错,但要通过它的名字来理解这个原理是相当困难的。
让我们从“关闭修改”开始。这意味着您可以更改类\函数\模块代码的唯一原因是直接更改嵌入其中的函数。全部。应该没有更多理由更改此代码。正是在这一点上,责任唯一性原则有了交集。
此外,开放扩展是什么意思?这意味着如果您需要您的类\功能\模块能够在新环境中执行其预期功能,它们必须支持它而不更改其代码。
让我们看一个例子来说明。
使用委托扩展类
假设我们有一个类来对数组进行排序
让我们看看这堂课。让我们试着了解它有哪些扩展点。扩展点是您的应用程序在代码的不同用例过程中可能遇到(或已经遇到)的那些需求。
例如,我们看到这个方法只对数字进行排序。但在 99% 的概率下,我们需要对除数字之外的其他内容进行排序。此外,排序只是按升序排序,但很可能我们需要按降序排序。事实上,我们再次与责任唯一性原则相交——我们确定类现在有哪些责任并尝试分担这些责任。让我们使用 dotnet 中现有的接口并稍微重写代码:
现在我们的分拣机更加灵活。现在,如果我们需要对字符串而不是数字进行排序,我们不必对排序器进行更改。如果我们需要按降序而不是升序排序,我们不必更改排序器代码。因此,我们可以使用新功能扩展我们的类,而无需实际更改类本身。
函数也是如此。例如,
程序模块也是如此(例如,当您可以在不同的场景中重用相同的模块时)。
但是,以上内容并不意味着您应该立即赶赴课堂并开始将此类技巧注入其中。与往常一样,在这样做之前,您需要用头脑思考。例如,您可以看到上面的代码只对数组进行排序。并就地排序(更改原始数组)。我故意省略了它,因为我想表明一个人不应该走极端。没有必要盲目地遵循原则,写东西之前要仔细考虑。因为如果你漫不经心地遵循这个原则,那么你最终可能会得到太抽象的代码,没有人会理解任何东西。
就我而言,我决定不需要对数组以外的其他数据类型进行排序。我假设我在当前的项目中不需要这个(将其视为综合示例的一部分)。
但是我们只考虑了扩展类的一种选择——将其部分责任转移到另一个可以动态替换的对象。我们还能用课堂做什么?
使用继承扩展类
假设我们在 CSV 文件中有一个典型的 writer 类
通过训练有素的眼睛,您可以看到在这种情况下,类本身决定了文件中对象条目的外观。当然,这可以作为单独的责任单独列出,但这与我们现在无关。但担心的是我们的类没有写文件头。也就是说,他可以以某种方式写下这些列,但每列都没有标题。该怎么办?从类函数的签名可以看出,它有一个虚方法,完全决定了数据写入文件的顺序。我们可以很容易地编写一个子类并向其添加文件头,而无需更改基类:
结果,功能得到了扩展,原来的类没有被触及。
使用聚合扩展类
因此,例如,我们有一个用于存储库的接口和一个类。您甚至可能拥有此类存储库的多个实现。
当然,这一切都有一些客户。
有一天,您的老板告诉您,您需要记录对存储库的每个实现的每次调用。但正因为如此,我不想改变所有的实现(你已经知道为什么了)。该怎么办?当然,推出一个新的包装器实现
她在干嘛?事实上,它接受装饰对象并通过日志记录对其进行代理调用。如果在您的代码看起来像这样之前:
现在它看起来像这样:
我们是否更改了存储库实现?不。您是否为它们添加了功能?是的。
有关类的类似技巧的更多信息here
因此,我想指出,我们只涉及了几个选项,用于在不影响实现的情况下扩展功能。其实还有更多的选择,理解这里的原理很重要——你写一个类\函数\模块,它们只做它们被创建的东西并扩展它们的功能而不影响它们的源代码。并且不要忘记责任唯一性原则——正如你应该注意到的,它与开放/封闭原则密切相关。
同样,您需要具有数据结构的灵活性以满足项目的要求,并且不要产生过度的灵活性,因为过度的灵活性会使您的抽象变得毫无意义。
如果你已经读到这里,那么我会告诉你一个很大的秘密——上面的每一种方法都使用一种或另一种模式。试着想想哪种模式在哪里,从最著名的模式书(来自帮派)中回顾主要模式,想想哪些模式扩展了类的功能,然后,希望马赛克将开始形成。