我知道SOLID原则是针对面向对象语言编写的。
我在书中找到了:"嵌入式C&#34的测试驱动开发;罗伯特马丁,本书最后一章中的以下句子:
"应用开放封闭原则和Liskov替代原则可以实现更灵活的设计。"
由于这是一本关于C(没有c ++或c#)的书,应该有一种方法来实现这个原则。
在C?
中存在实现这一原则的任何标准方法答案 0 :(得分:10)
开放式原则指出系统应该设计成可以在保持关闭而无需修改的情况下对扩展开放,或者可以在不修改的情况下使用和扩展系统。 Dennis提到的I / O子系统是一个相当常见的例子:在可重用系统中,用户应该能够指定如何读取和写入数据,而不是假设数据只能写入文件。
实现这一点的方法取决于您的需求:您可以允许用户传入一个打开的文件描述符或句柄,除了文件之外,它还支持使用套接字或管道。或者您可以允许用户将指针传递给应该用于读写的函数:这样,除了操作系统允许的内容外,您的系统还可以与加密或压缩数据流一起使用。
Liskov替换原则指出应始终可以用子类型替换类型。在C中,您通常不具有子类型,但您可以在模块级别应用该原则:应该设计代码,以便使用模块的扩展版本(如较新版本)不应该破坏它。模块的扩展版本可能使用struct
,其中包含的字段多于原始字段,enum
中的更多字段以及类似内容,因此您的代码不应假设传入的结构具有一定的大小,或者枚举值具有一定的最大值。
这方面的一个例子是如何在BSD套接字API中实现套接字地址:有一个“抽象”套接字类型struct sockaddr
,可以代表任何套接字地址类型,以及每个实现的具体套接字类型,例如对于Unix域套接字为struct sockaddr_un
,对于IP套接字为struct sockaddr_in
。必须将对套接字地址起作用的函数传递给指向数据和的指针,具体地址类型的大小。
答案 1 :(得分:3)
首先,考虑为什么我们有这些设计原则是有帮助的。为什么遵循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)
的内容,我们也保留了它的功能。它仍然没有改变。
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()应该没有隐藏的依赖关系,或者如果它们这样做,它们也必须兼容。在这个简单的例子中,两个函数都只依赖于stdout,所以这并不是什么大问题。我们的想法是,在两种功能可以互换使用的情况下,您可以保留正确的行为。
答案 2 :(得分:1)
我能想到的最接近我头脑的东西(并且它不完美,所以如果有人有更好的想法,欢迎他们一个人),主要是因为当我为某些人写作时某种图书馆。
对于Liskov替换,如果您有一个定义了许多函数的头文件,那么您不希望该库的功能依赖于您对函数的哪些实现;你应该能够使用任何合理的实现,并希望你的程序能够做到这一点。
对于开放/封闭原则,如果要实现I / O库,则希望具有最低限度的函数(如read
和write
)。同时,您可能希望使用它们来开发更复杂的I / O函数(如scanf
和printf
),但您不会修改最低限度的代码。
答案 3 :(得分:-1)
我认为问题已经开启已有一段时间了,但我认为值得一些新的看法。
五个SOLID原则指的是软件实体的五个方面,如SOLID diagram中所示。虽然这是一个类图,但它基本上可以服务于其他类型的SW身份。为调用者公开的接口(左箭头,代表接口隔离)和作为被调用者请求的接口(右箭头,代表依赖性反转)也可以是经典的C函数和参数接口。
顶部箭头(扩展箭头,代表Liskov替换原则)适用于类似实体的任何其他实现。例如,如果您有一个链接列表的API,您可以更改其函数的实现,甚至可以更改向量“对象”的结构(例如,假设它保留了原始结构的结构,就像在BSD中一样)套接字示例,或者它是一个不透明的类型)。当然,这不像OOP语言中的Object那么优雅,但它遵循相同的原则,并且可以使用,例如,使用动态链接。
以类似的方式,底部箭头(概括箭头,代表打开/关闭原则)定义磨轮由您的实体定义,什么是开放。例如,某些功能可能在一个文件中定义,不应替换,而其他功能可能会调用另一组API,这允许使用不同的实现。
这样你也可以用C编写SOLID SW,虽然它可能会使用更高级别的实体来完成,可能需要更多的工程。