Osc*_*nco 13 c c++ design-principles solid-principles c++11
我知道SOLID原则是针对面向对象语言编写的.
我在书中找到了罗伯特·马丁的"嵌入式C的测试驱动开发",本书最后一章的后续句子:
"应用开放封闭原则和Liskov替代原则可以实现更灵活的设计."
由于这是一本C(无c ++或c#)的书,应该有一种方法来实现这个原则.
在C中实现这个原则有什么标准方法吗?
Jon*_*oni 10
开放 - 封闭原则指出,系统应该被设计成可以在保持关闭而不被修改的情况下对扩展开放,或者可以在不修改它的情况下使用和扩展系统.Dennis提到的I/O子系统是一个相当常见的例子:在可重用系统中,用户应该能够指定如何读取和写入数据,而不是假设数据只能写入文件.
实现这一点的方法取决于您的需求:您可以允许用户传入一个打开的文件描述符或句柄,除了文件之外,它还允许使用套接字或管道.或者您可以允许用户将指针传递给应该用于读写的函数:这样,除了操作系统允许的内容外,您的系统还可以与加密或压缩数据流一起使用.
Liskov替换原则指出应始终可以用子类型替换类型.在C中,您通常不具有子类型,但您可以在模块级别应用该原则:应该设计代码,以便使用模块的扩展版本(如较新版本)不应该破坏它.模块的扩展版本可以使用struct
具有比原始字段更多的字段,更多字段enum
和类似内容的字段,因此您的代码不应该假定传入的结构具有特定大小,或者枚举值具有一定的最大值.
这方面的一个例子是如何在BSD套接字API中实现套接字地址:有一个"抽象"套接字类型struct sockaddr
,可以代表任何套接字地址类型,以及每个实现的具体套接字类型,例如struct sockaddr_un
Unix域套接字和struct sockaddr_in
IP插座.处理套接字地址的函数必须传递指向数据的指针和具体地址类型的大小.
首先,它有助于思考为什么我们有这些设计原则。为什么遵循 SOLID 原则会让软件变得更好?努力理解每个原则的目标,而不仅仅是将它们与特定语言一起使用所需的具体实现细节。
请注意每个原则如何推动系统某个属性的改进,无论是更高的内聚性、更松散的耦合还是模块化。
请记住,您的目标是生产高质量的软件。质量由许多不同的属性组成,包括正确性、效率、可维护性、可理解性等。遵循 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)
,我们仍保留了它的功能。它仍然拒绝改变。
里氏替换原则指出不同的子类型必须兼容地工作。换句话说,具有共同特征、可以相互替代的事物在理性上必须表现得相同。
在 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;
}
Run Code Online (Sandbox Code Playgroud)
当然,fred() 和 barney() 必须具有兼容的参数列表才能工作,但这与子类从其超类继承其 vtable 没有什么不同。行为契约的一部分是 fred() 和 barney() 不应该有隐藏的依赖关系,或者如果有的话,它们也必须兼容。在这个简单的示例中,两个函数都仅依赖于 stdout,因此这并不是什么大问题。这个想法是,在这两种函数可以互换使用的情况下,您都可以保留正确的行为。