打印
[其它应用]

嵌入式软件设计中的依赖反转原则

[复制链接]
19|2
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
flycamelaaa|  楼主 | 2024-11-26 15:17 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
在软件设计中,依赖反转原则(英文:Dependency Inversion Principle, DIP)是面向对象设计原则的一部分,旨在提高系统的灵活性和可维护性。
虽然C语言本身不是直接支持面向对象无法特性的语言,但我们仍然可以在C语言中实现类似的概念和选择思想。
一、依赖反转原则基本思想这个原则的主要思想就两点:
高层模块不应依赖于低层模块,两者都应依赖于抽象(接口)。
抽象不应依赖于细节,细节应依赖于抽象。
这句话怎么理解呢?
以图形绘制系统为例,绘图的抽象类或者接口不依赖于具体的图形(如圆形、矩形这些细节)如何绘制。相反,具体图形的绘制类要实现绘图接口规定的方法,即细节依赖抽象。这使得系统更灵活,方便添加新图形种类。
在C语言中的实现
虽然C语言没有类和接口的概念,但可以通过函数指针来实现依赖反转。
以下是一个简单的例子:

#include <stdio.h>

// 定义一个函数指针类型,用于表示抽象行为
typedef void (*LogFunction)(const char*);

// 低层模块:具体实现
void ConsoleLogger(const char* message) {
    printf("Console: %s\n", message);
}

void FileLogger(const char* message) {
    // 假设将消息写入文件的代码
    printf("File: %s\n", message);
}

// 高层模块
void Application(LogFunction logger) {
    logger("Hello, Dependency Inversion!");
}

int main() {
    // 使用控制台日志记录
    Application(ConsoleLogger);
   
    // 使用文件日志记录
    Application(FileLogger);
   
    return 0;
}
代码简单分析如下:
LogFunction:定义了一个函数指针类型,用于实现不同的日志记录策略。
ConsoleLogger 和 FileLogger:这两个函数是低层模块,负责具体的日志记录实现。
Application:这是高层模块,它依赖于LogFunction类型的抽象,而不是具体的日志实现。

使用特权

评论回复
沙发
flycamelaaa|  楼主 | 2024-11-26 15:17 | 只看该作者
二、相对复杂一点的例子 以下是一个简单的C语言示例来体现依赖反转原则:
#include <stdio.h>

// 抽象接口:定义了形状的绘制操作
typedef struct Shape Shape;
struct Shape {
    void (*draw)(Shape*);
};

// 具体形状:圆形
typedef struct Circle {
    Shape base;
    double radius;
} Circle;

// 圆形的绘制实现
void circle_draw(Shape* shape) {
    Circle* circle = (Circle*)shape;
    printf("Drawing a circle with radius %.2f\n", circle->radius);
}

// 具体形状:矩形
typedef struct Rectangle {
    Shape base;
    double width;
    double height;
} Rectangle;

// 矩形的绘制实现
void rectangle_draw(Shape* shape) {
    Rectangle* rectangle = (Rectangle*)shape;
    printf("Drawing a rectangle with width %.2f and height %.2f\n", rectangle->width, rectangle->height);
}

// 主函数,用于测试
int main() {
    // 创建圆形对象并初始化
    Circle circle;
    circle.base.draw = circle_draw;
    circle.radius = 5.0;

    // 创建矩形对象并初始化
    Rectangle rectangle;
    rectangle.base.draw = rectangle_draw;
    rectangle.width = 4.0;
    rectangle.height = 6.0;

    // 通过抽象接口调用绘制操作
    Shape* shapes[2];
    shapes[0] = (Shape*)&circle;
    shapes[1] = (Shape*)&rectangle;

    for (int i = 0; i < 2; i++) {
        shapes[i]->draw(shapes[i]);
    }

    return 0;
}
 
首先定义了一个抽象的  Shape  结构体,它包含一个函数指针  draw ,代表了绘制形状的抽象操作,这相当于依赖反转原则中的抽象部分。
然后分别定义了具体的形状  Circle  和  Rectangle ,它们都包含了  Shape  结构体作为其一部分,并各自实现了对应的绘制函数( circle_draw  和  rectangle_draw ),这是细节依赖抽象的体现。
在  main  函数中,可以通过指向  Shape  结构体的指针数组来统一处理不同的具体形状对象,而不需要关心它们具体是哪种形状,只需要调用抽象接口中定义的  draw  操作即可。
这样高层模块(这里的  main  函数可以看作相对高层的模块,负责协调不同形状的绘制)不依赖于具体的形状模块(圆形或矩形的具体实现),而是依赖于抽象的  Shape  接口,符合依赖反转原则。
如果后续要添加新的形状,只需按照类似的方式定义新形状结构体并实现对应的绘制函数,使其符合  Shape  抽象接口,就可以很方便地集成到系统中。

使用特权

评论回复
板凳
flycamelaaa|  楼主 | 2024-11-26 15:17 | 只看该作者
三、总   结通过使用函数指针,我们可以在C语言中实现依赖反转原则。这种方法使得高层模块与低层模块解耦,提高了系统的灵活性和可测试性。
灵活性
当系统的某个具体模块(细节)需要修改时,只要它所实现的抽象接口不变,依赖于这个抽象接口的其他模块几乎不需要修改。
例如,在一个物流系统中,运输方式(如陆运、水运)的具体实现细节改变,只要运输方式的抽象接口(如计算运费、预计到达时间)不变,依赖该接口的订单管理等高层模块不用修改,维护起来更方便。
可测试性增强
可以方便地使用模拟对象(Mock)来替代真实的依赖对象进行单元测试。比如在开发一个支付系统时,对于支付网关这个依赖项,在测试时可以通过模拟一个符合抽象接口规范的假支付网关,来测试支付处理模块的功能,而不用依赖真实的支付网关,使得测试更容易进行。

软件的可扩展性变好
便于添加新功能。在遵循依赖反转原则的软件架构中,添加新的功能模块(细节)只需要新模块实现现有的抽象接口就行。例如,在一个图形编辑软件中,要添加新的图形绘制功能,只要新的图形绘制类实现绘图的抽象接口,就可以方便地集成到系统中,而不会对其他模块产生较大影响。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

657

主题

2745

帖子

0

粉丝