首先,思考为什么我们有这些设计原则会有所帮助。为什么遵循 SOLID 原则会使软件变得更好?努力了解每个原则的目标,而不仅仅是将它们与特定语言一起使用所需的具体实现细节。
- 单一职责原则通过增加
凝聚;更好的模块化导致改进的可测试性,
可用性和可重用性。
- Open/Closed 原则通过以下方式实现异步部署
实现相互解耦。
- Liskov 替换原则通过以下方式促进模块的模块化和重用
确保其接口的兼容性。
- 接口隔离原则减少了之间的耦合
界面的无关消费者,同时增加可读性和
易于理解。
- 依赖倒置原则减少了耦合,并且它强
实现可测试性。
请注意每个原则如何推动系统特定属性的改进,无论是更高的内聚、更松散的耦合还是模块化。
请记住,您的目标是制作高质量的软件。质量由许多不同的属性组成,包括正确性、效率、可维护性、可理解性等。遵循 SOLID 原则可以帮助您实现目标。因此,一旦您了解了原则的“原因”,实施的“方式”就会变得容易得多。
编辑:
我会尝试更直接地回答您的问题。
对于打开/关闭原则,规则是旧接口的签名和行为在任何更改之前和之后都必须保持不变。不要破坏任何调用它的代码。这意味着它绝对需要一个新的接口来实现新的东西,因为旧的东西已经有了一个行为。新界面必须具有不同的签名,因为它提供了新的和不同的功能。因此,您在 C 中满足这些要求的方式与在 C++ 中相同。
假设您有一个函数int foo(int a, int b, int c),并且您想添加一个几乎完全相同的版本,但它需要第四个参数,例如:int foo(int a, int b, int c, int d)。要求新版本与旧版本向后兼容,并且新参数的某些默认值(例如零)会实现这一点。您将实现代码从旧 foo 移动到新 foo 中,并且在旧 foo 中您会这样做:int foo(int a, int b, int c) { return foo(a, b, c, 0);} 因此,即使我们从根本上改变了 int foo(int a, int b, int c) 的内容,我们仍然保留了它的功能。它仍然无法更改。
Liskov 替换原则指出不同的子类型必须兼容工作。换句话说,具有共同签名且可以相互替代的事物在理性上必须表现相同。
在 C 中,这可以通过指向具有相同参数集的函数的函数指针来实现。假设您有以下代码:
#include <stdio.h>
void fred(int x)
{
printf( "fred %d\n", x );
}
void barney(int x)
{
printf( "barney %d\n", x );
}
#define Wilma 0
#define Betty 1
int main()
{
void (*flintstone)(int);
int wife = Betty;
switch(wife)
{
case Wilma:
flintstone = &fred;
case Betty:
flintstone = &barney;
}
(*flintstone)(42);
return 0;
}
fred() 和 barney() 当然必须具有兼容的参数列表才能使其工作,但这与子类从其超类继承其 vtable 没有什么不同。行为契约的一部分是 fred() 和 barney() 都应该没有隐藏的依赖关系,或者如果它们有,它们也必须兼容。在这个简单的例子中,两个函数都只依赖于标准输出,所以这没什么大不了的。这个想法是在两种情况下都可以保持正确的行为,其中任一功能都可以互换使用。