《深入设计模式》阅读笔记
本文主要总结设计模式这本书,并用c语言简单举例
一、前提
1.本书背景及重要性
这本书由亚历山大·什韦茨(Alexander Shvets)撰写,深入探讨了23种经典的设计模式。它不仅介绍了设计模式的定义、分类和应用场景,还通过具体的代码示例和UML图,帮助读者更好地理解和应用这些模式。
1.背景
设计模式是软件设计中常见问题的典型解决方案,它们就像能根据需求进行调整的预制蓝图,可用于解决代码中反复出现的设计问题。自1994年“四人组(GoF)”的《设计模式:可复用面向对象软件的基础》一书出版以来,设计模式逐渐成为软件开发领域的重要组成部分。随着软件开发技术的不断发展,新的设计模式不断涌现,原有的设计模式也在不断演进和完善。因此,深入研究设计模式对于提高软件设计的质量和效率具有重要意义。
2.重要性
- 提高软件的可复用性:设计模式是软件开发中的经验总结,它们提供了一种通用的解决方案,可以在不同的项目中重复使用。通过使用设计模式,可以减少代码的重复编写,提高代码的复用率,从而降低软件开发的成本和风险。
- 提高软件的可维护性:设计模式通常具有良好的结构和组织,它们将复杂的系统分解为简单的模块,使得软件的维护更加容易。通过使用设计模式,可以提高软件的可读性、可扩展性和可维护性,从而降低软件维护的成本和风险。
- 提高软件的开发效率:设计模式提供了一种通用的解决方案,可以在不同的项目中重复使用。通过使用设计模式,可以减少代码的重复编写,提高代码的复用率,从而提高软件开发的效率。
- 促进团队协作:设计模式是软件开发中的经验总结,它们提供了一种通用的解决方案,可以在不同的项目中重复使用。通过使用设计模式,可以提高团队成员之间的沟通和协作效率,从而提高团队的整体素质和竞争力。
2.基础概念
1. + 和 - 符号表示
+表示公有
-表示私有
2. Interface概念
interface代表对象的共有部分
基于接口的实现,规定一类对象的行为
如果父类实现了某个接口,子类必须实现该接口;子类继承父类的目的是对父类的拓展,无法裁剪;
开闭原则的基础,实现拒绝修改,支持拓展
3. 蓝图**(Blueprint)**
蓝图通常指的是系统或应用的高层设计。它描述了系统的架构、组件、模块以及它们之间的关系和交互。蓝图更多关注的是整体结构和规划,而不是具体的实现细节。
二、设计模式的定义和分类
1.设计模式的概念
设计模式是软件设计中常见问题的典型解决方案,它们就像能根据需求进行调整的预制蓝图,可用于解决代码中反复出现的设计问题。
设计模式并不是一段特定的代码,而是解决特定问题的一般性概念。它提供了一种通用的解决方案,可以在不同的项目中重复使用。设计模式的目的是提高软件的可复用性、可维护性和可读性,使软件设计更加灵活、高效和可靠。
设计模式通常具有以下特点:
- 通用性:设计模式是针对软件设计中常见问题的解决方案,具有广泛的适用性。
- 可复用性:设计模式提供了一种通用的解决方案,可以在不同的项目中重复使用,减少代码的重复编写。
- 灵活性:设计模式可以根据具体的需求进行调整和扩展,具有较高的灵活性。
- 可读性:设计模式提供了一种清晰、简洁的设计思路,使代码易于理解和维护。
2.模式构成
意图 | 描述问题和解决方案 |
---|---|
动机 | 解释问题并说明模式会如何提供解决方案 |
结构 | 展示模式的各个部分和它们之间的关系 |
实现 | 在不同语言中的实现 |
3.模式和算法的区别
算法像菜谱,提供达成目标的明确步骤。模式像蓝图[ ]:可以看到最终的结果和模式的功能,但需要自己确定实现步骤
4.设计模式的分类
设计模式可以分为创建型模式、结构型模式和行为型模式三大类。创建型模式主要用于创建对象,结构型模式主要用于组织对象,行为型模式主要用于协调对象之间的交互。
创建型模式 | 提供创建对象的机制,增加已有代码的灵活性和可复用性 |
---|---|
结构型模式 | 提供如何将对象和类组成较大的结构,同时保持结构的灵活性和高效 |
行为模式 | 负责对象间的高效沟通和职责委派 |
创建型模式
创建型模式提供了创建对象的机制,能够提升已有代码的灵活性和可复用性。创建型模式包括工厂方法模式、抽象工厂模式、生成器模式、原型模式和单例模式。
结构型模式
结构型模式介绍了如何将对象和类组装成较大的结构,并同时保持结构的灵活和高效。结构型模式包括适配器模式、桥接模式、组合模式、装饰模式、外观模式、享元模式和代理模式。
行为型模式
行为型模式负责对象间的高效沟通和职责委派。行为型模式包括责任链模式、命令模式、迭代器模式、中介者模式、备忘录模式、观察者模式、状态模式、策略模式、模板方法模式和访问者模式。
三、设计原则
优秀设计的特征
在软件开发中,优秀的设计应该具备以下特征(设计原则):
代码复用:通过复用已有的代码,可以减少代码的重复编写,提高开发效率,降低维护成本。
扩展性:设计应该能够适应未来的变化和扩展,以便在需要时能够轻松地添加新的功能或修改现有的功能。
单一职责原则:一个类应该只有一个引起它变化的原因,也就是说,一个类应该只负责一项职责。
开闭原则:软件实体应该对扩展开放,对修改关闭。也就是说,在不修改现有代码的情况下,可以通过添加新的代码来扩展软件的功能。
里氏替换原则:当一个子类的对象可以替换其父类的对象时,软件的行为不会发生变化。也就是说,子类应该能够完全替代父类,而不会影响软件的正确性。
接口隔离原则:客户端不应该被迫依赖于它们不使用的方法。也就是说,应该将接口拆分成更小的接口,以便客户端只需要依赖它们实际使用的接口。
依赖倒置原则:高层模块不应该依赖于低层模块,二者都应该依赖于抽象。也就是说,应该将高层模块和低层模块之间的依赖关系倒置过来,使得高层模块依赖于抽象,而不是具体的实现。
(实际上就是面向接口开发)
在C语言中,我们可以将汽车和引擎的关系作为依赖倒置原则的例子。在这个例子中,汽车是一个高层模块,引擎是一个低层模块。根据依赖倒置原则,汽车不应该直接依赖于具体的引擎实现,而是应该依赖于引擎的抽象接口。
下面是一个简单的C语言代码示例:
#include <stdio.h> // Engine interface typedef void(*EngineStart)(); typedef void(*EngineStop)(); typedef struct Engine { EngineStart start; EngineStop stop; } Engine; // Concrete engine implementation void startConcreteEngine() { printf("Concrete engine started.\\n"); } void stopConcreteEngine() { printf("Concrete engine stopped.\\n"); } Engine createConcreteEngine() { Engine engine = { .start = startConcreteEngine, .stop = stopConcreteEngine }; return engine; } // Car class typedef struct Car { Engine* engine; } Car; Car createCar(Engine* engine) { Car car = { .engine = engine }; return car; } void startCar(Car* car) { car->engine->start(); } void stopCar(Car* car) { car->engine->stop(); } int main() { Engine concreteEngine = createConcreteEngine(); Car car = createCar(&concreteEngine); startCar(&car); stopCar(&car); return 0; }
在这个例子中,
Engine
是一个抽象接口,ConcreteEngine
是一个具体的实现。Car
类依赖于Engine
接口,而不是具体的ConcreteEngine
实现。这样,如果我们要更换汽车的引擎,我们只需要创建一个新的引擎实现,并将其传递给Car
构造函数,而无需修改Car
类的代码。这就是依赖倒置原则的应用。封装变化的内容
“封装变化的内容” (pdf)
面向接口进行开发,而不是面向实现
“面向接口进行开发, 而不是面向实现” (pdf)
组合优于继承 “组合优于继承” (pdf)
“在重写方法时, 你需要确保新行为与其基类中的版本兼容。 这一点很重要, 因为子类的所有对象都可能被传递给以超类 对象为参数的任何代码, 相信你不会希望这些代码崩溃的。” (pdf) 当你重写一个方法时,确保新行为与基类中的版本兼容意味着你需要保持方法的签名(参数列表和返回类型)相同,以及保持方法的预期行为相似。这是因为在面向对象编程中,子类对象可以被当作其超类对象使用,而超类中的方法可能会被其他代码调用,这些代码可能无法区分子类和超类的对象。
举个例子,假设有一个动物园程序,里面有一个
feed_animal
函数,它接受一个动物对象并喂养它。如果我们在子类中重写了动物的eat
方法,我们需要确保新的eat
方法与超类中的eat
方法具有相同的签名和类似的行为,以便feed_animal
函数在处理子类对象时不会出现问题。class Animal: def eat(self): print("Animal is eating") class Dog(Animal): def eat(self): print("Dog is eating") \## feed_animal 函数接受一个 Animal 对象并喂养它def feed_animal(animal): animal.eat() \## 创建一个 Dog 对象dog = Dog() \## 调用 feed_animal 函数并传递 Dog 对象feed_animal(dog)
在这个例子中,
Dog
类重写了eat
方法,但它的签名和预期行为与超类Animal
中的eat
方法相同,因此feed_animal
函数在处理Dog
对象时不会出现问题。如果
Dog
类中的eat
方法签名或行为与Animal
类中的不同,那么feed_animal
函数在处理Dog
对象时可能会出现意料之外的行为或错误。因此,确保在重写方法时与超类中的版本兼容是很重要的。“通过继承复用代码可能导致平行继承体系的产生。” (pdf) 你提到的这种情况通常被称为”多重继承”或”平行继承体系”,它可能会导致类层次结构的复杂性增加,从而使代码难以维护和理解。这种情况通常出现在具有多个功能维度的系统中,因为每个功能维度可能都需要一组不同的特性或行为。
让我们通过一个简单的代码示例来说明这个概念。假设我们正在设计一个图形界面库,其中有两个主要功能维度:控件类型和主题样式。我们希望能够创建不同类型的控件(如按钮、文本框)并为它们应用不同的主题样式(如浅色主题、深色主题)。
在这种情况下,如果我们使用了多重继承,可能会导致类层次结构的爆炸性增长。让我们看一个简化的示例:
class Widget: def draw(self): pass class Button(Widget): def click(self): pass class TextBox(Widget): def edit(self): pass class LightTheme: def apply(self, widget): pass class DarkTheme: def apply(self, widget): pass class LightButton(Button, LightTheme): pass class DarkButton(Button, DarkTheme): pass class LightTextBox(TextBox, LightTheme): pass class DarkTextBox(TextBox, DarkTheme): pass
在上面的示例中,我们创建了四个类来表示不同类型的控件和主题样式组合。如果我们有更多的控件类型和主题样式,类层次结构将会急剧膨胀。
为了避免这种情况,通常会使用其他设计模式,如组合或策略模式,来减轻类层次结构的压力。这些模式允许我们将行为和特性组合起来,而不是通过继承来扩展类。这样可以更灵活地管理代码,并且不会导致类层次结构的爆炸性增长。
“组合是代替继承的一种方法。 继承代表类之间的“是” 关系 (汽车是交通工具), 而组合则代表“有” 关系(汽车有一个 引擎)” (pdf) 继承is是,组合is有
好莱坞原则” (pdf)
“好莱坞原则” (pdf) 好莱坞原则是一种设计原则,它指导着框架与应用程序代码之间的交互方式。在这种原则下,框架控制着应用程序的执行流程,而应用程序代码则通过特定的方式与框架进行交互。具体来说,当需要执行某些操作时,框架会调用应用程序代码,而应用程序代码不能主动调用框架。
让我们以JUnit为例来解释这个原则。JUnit是一个用于编写和运行Java单元测试的框架。在JUnit中,你可以编写测试用例,然后JUnit框架负责执行这些测试用例,并向你报告测试结果。在这个过程中,你的测试用例类会继承JUnit提供的测试类,并且你会覆盖一些方法或者使用注解来标记测试方法。
下面是一个简单的JUnit测试用例的示例:
import static org.junit.Assert.assertEquals;
import org.junit.Test;
public class MyTestClass {
@Test
public void testAddition() {
int result = 2 + 2;
assertEquals(4, result);
}
}
在这个例子中,MyTestClass
是一个简单的测试类,它包含了一个测试方法testAddition()
。当你运行这个测试类时,JUnit框架会负责调用testAddition()
方法,并检查其结果是否符合预期。
这符合好莱坞原则的精神,因为测试方法的调用是由JUnit框架触发的,而不是由测试类自身触发的。换句话说,测试类告诉JUnit框架它有哪些测试方法,并且JUnit框架在需要执行这些方法时会主动调用它们。
“设计 模式比框架更小且更抽象。 它们实际上是对一组类的关系及 其互动方式的描述。” (pdf) 模式的本质
“中间层次的优点在于模式提供的复用方式要比框架的风险小。” (pdf) 中间层次的设计模式提供了一种介于框架和单个类之间的解决方案。它通常涉及将一些常见的模式、结构或者逻辑抽象出来,形成中间层次的组件,以便在不同的应用场景中进行复用。
相比之下,框架提供了一个更为全面和高级的解决方案,它通常包含了整个应用程序的基础架构,定义了应用程序的整体结构和行为。因此,在使用框架时,你必须遵循框架所定义的规则和约定,这可能会限制你的灵活性,并且需要花费更多的时间来学习和理解整个框架的工作原理。
中间层次的优点在于:
更小的风险: 使用中间层次的设计模式,你可以更加灵活地选择性地应用一些特定的模式或者组件,而不需要依赖整个框架。这样可以降低引入新技术或者更改现有逻辑所带来的风险。因为中间层次的组件通常比整个框架更为简单和可控,你可以更容易地理解和管理这些组件。
更高的可维护性: 中间层次的组件通常更加模块化和可组合,这使得它们更易于维护和扩展。你可以根据具体的需求选择性地替换或者更新中间层次的组件,而不会对整个应用程序产生太大的影响。
举个例子,假设你正在开发一个Web应用程序,你可以选择使用一个完整的Web框架,比如Django或者Spring,来构建整个应用程序的基础架构。然而,如果你只需要处理一些简单的HTTP请求和响应,你也可以选择使用中间层次的组件,比如WSGI(Python Web Server Gateway Interface)或者Servlets(Java Servlets),来处理这些请求和响应,而不需要引入整个框架的复杂性和约束。这样可以减少开发和维护的成本,并且降低引入新技术所带来的风险。
四、创建型模式
工厂方法模式
一、定义
工厂方法模式是一种创建型设计模式,它定义了一个创建对象的接口,但让子类决定实例化哪个类。工厂方法模式将对象的创建与使用分离,使得系统更加灵活和可扩展。
二、结构
- 抽象工厂类:定义了一个创建产品的接口,其中包含一个抽象的工厂方法。
- 具体工厂类:实现了抽象工厂类中的工厂方法,负责创建具体的产品对象。
- 抽象产品类:定义了产品的共性,包含一个抽象的产品方法。
- 具体产品类:实现了抽象产品类中的产品方法,是具体的产品对象。
三、示例代码
以下是一个使用工厂方法模式创建汽车的 C 语言示例:
#include <stdio.h>
// 定义汽车类型枚举
typedef enum {
SEDAN,
SUV,
HATCHBACK
} CarType;
// 汽车结构体
typedef struct {
char* model;
int year;
} Car;
// 抽象工厂函数
Car* createCar(CarType type) {
switch (type) {
case SEDAN:
return createSedan();
case SUV:
return createSUV();
case HATCHBACK:
return createHatchback();
default:
return NULL;
}
}
// 创建轿车
Car* createSedan() {
Car* sedan = (Car*)malloc(sizeof(Car));
sedan->model = "Sedan Model";
sedan->year = 2023;
return sedan;
}
// 创建 SUV
Car* createSUV() {
Car* suv = (Car*)malloc(sizeof(Car));
suv->model = "SUV Model";
suv->year = 2023;
return suv;
}
// 创建掀背车
Car* createHatchback() {
Car* hatchback = (Car*)malloc(sizeof(Car));
hatchback->model = "Hatchback Model";
hatchback->year = 2023;
return hatchback;
}
int main() {
// 创建轿车
Car* sedan = createCar(SEDAN);
printf("Created Sedan: %s, %d\\n", sedan->model, sedan->year);
// 创建 SUV
Car* suv = createCar(SUV);
printf("Created SUV: %s, %d\\n", suv->model, suv->year);
// 创建掀背车
Car* hatchback = createCar(HATCHBACK);
printf("Created Hatchback: %s, %d\\n", hatchback->model, hatchback->year);
// 释放内存
free(sedan);
free(suv);
free(hatchback);
return 0;
}
在上述示例中,我们定义了一个汽车类型枚举CarType
,表示不同类型的汽车。然后,我们定义了一个汽车结构体Car
,包含汽车的模型和年份信息。
接下来,我们定义了一个抽象工厂函数createCar
,它根据传入的汽车类型参数创建相应类型的汽车。在函数内部,我们使用switch
语句根据汽车类型创建具体的汽车对象,并返回创建的汽车指针。
然后,我们分别实现了创建轿车、SUV 和掀背车的具体函数createSedan
、createSUV
和createHatchback
,它们分别创建相应类型的汽车对象,并设置汽车的模型和年份信息。
在main
函数中,我们使用createCar
函数创建了三种不同类型的汽车,并打印出汽车的信息。最后,我们使用free
函数释放了创建的汽车对象所占用的内存。
通过使用工厂方法模式,我们将汽车的创建过程封装在抽象工厂函数中,使得客户端代码无需关心具体汽车类型的创建细节,只需要调用抽象工厂函数即可创建所需类型的汽车对象。这样可以提高代码的灵活性和可扩展性,方便后续添加新的汽车类型或修改汽车的创建方式。
四、优点
- 灵活性:工厂方法模式将对象的创建与使用分离,使得系统更加灵活。客户端可以根据需要选择不同的工厂类来创建不同的产品对象,而无需关心产品对象的具体实现细节。
- 可扩展性:工厂方法模式允许添加新的工厂类和产品类,而无需修改现有代码。只需要在新的工厂类中实现
createShape
方法,就可以创建新的产品对象。 - 封装性:工厂方法模式将产品对象的创建过程封装在工厂类中,使得客户端无法直接创建产品对象,从而提高了系统的封装性和安全性。
五、缺点
- 增加了系统的复杂度:工厂方法模式需要定义抽象工厂类、具体工厂类、抽象产品类和具体产品类,增加了系统的复杂度。
- 可能会导致类的数量过多:如果系统中需要创建的产品种类较多,那么可能会导致工厂类和产品类的数量过多,从而增加了系统的维护成本。
六、适用场景
- 当需要创建的对象种类较多,且它们的创建过程比较复杂时,可以使用工厂方法模式。
- 当需要在不同的条件下创建不同的对象时,可以使用工厂方法模式。
- 当需要将对象的创建过程与使用过程分离时,可以使用工厂方法模式。
七、总结
工厂方法模式是一种创建型设计模式,它定义了一个创建对象的接口,但让子类决定实例化哪个类。工厂方法模式将对象的创建与使用分离,使得系统更加灵活和可扩展。工厂方法模式的优点是灵活性、可扩展性和封装性,缺点是增加了系统的复杂度和可能会导致类的数量过多。工厂方法模式适用于需要创建的对象种类较多、在不同的条件下创建不同的对象以及将对象的创建过程与使用过程分离的场景。
抽象工厂模式
一、定义
抽象工厂模式是一种创建型设计模式,它能创建一系列相关的对象,而无需指定其具体类。
二、结构
- 抽象产品:定义了产品的共性,是具体产品的父类。
- 具体产品:实现了抽象产品的接口,是抽象产品的具体实现。
- 抽象工厂:声明了一组创建抽象产品的方法,是具体工厂的父类。
- 具体工厂:实现了抽象工厂的构建方法,负责创建具体的产品对象。
三、示例代码
#include <stdio.h>
// 定义汽车类型枚举
typedef enum {
SEDAN,
SUV,
HATCHBACK
} CarType;
// 汽车结构体
typedef struct {
char* model;
int year;
} Car;
// 抽象工厂函数
Car* createCar(CarType type) {
switch (type) {
case SEDAN:
return createSedan();
case SUV:
return createSUV();
case HATCHBACK:
return createHatchback();
default:
return NULL;
}
}
// 创建轿车
Car* createSedan() {
Car* sedan = (Car*)malloc(sizeof(Car));
sedan->model = "Sedan Model";
sedan->year = 2023;
return sedan;
}
// 创建 SUV
Car* createSUV() {
Car* suv = (Car*)malloc(sizeof(Car));
suv->model = "SUV Model";
suv->year = 2023;
return suv;
}
// 创建掀背车
Car* createHatchback() {
Car* hatchback = (Car*)malloc(sizeof(Car));
hatchback->model = "Hatchback Model";
hatchback->year = 2023;
return hatchback;
}
int main() {
// 创建轿车
Car* sedan = createCar(SEDAN);
printf("Created Sedan: %s, %d\\n", sedan->model, sedan->year);
// 创建 SUV
Car* suv = createCar(SUV);
printf("Created SUV: %s, %d\\n", suv->model, suv->year);
// 创建掀背车
Car* hatchback = createCar(HATCHBACK);
printf("Created Hatchback: %s, %d\\n", hatchback->model, hatchback->year);
// 释放内存
free(sedan);
free(suv);
free(hatchback);
return 0;
}
在上述示例中,我们定义了一个抽象工厂函数createCar
,它根据传入的汽车类型参数创建相应类型的汽车。在函数内部,我们使用switch
语句根据汽车类型创建具体的汽车对象,并返回创建的汽车指针。
然后,我们分别实现了创建轿车、SUV 和掀背车的具体函数createSedan
、createSUV
和createHatchback
,它们分别创建相应类型的汽车对象,并设置汽车的模型和年份信息。
在main
函数中,我们使用createCar
函数创建了三种不同类型的汽车,并打印出汽车的信息。最后,我们使用free
函数释放了创建的汽车对象所占用的内存。
四、优点
- 封装性:抽象工厂模式将产品的创建过程封装在抽象工厂类中,使得客户端代码无需关心产品的具体创建过程,只需要通过抽象工厂类提供的方法来获取产品即可。
- 可扩展性:抽象工厂模式支持产品族的扩展,当需要添加新的产品族时,只需要创建新的具体工厂类即可,无需修改现有代码。
- 灵活性:抽象工厂模式可以根据不同的需求创建不同的产品族,从而提高了系统的灵活性。
五、缺点
- 抽象工厂类的设计难度较大:抽象工厂类需要定义多个抽象方法来创建不同的产品,这些方法的参数和返回值类型需要根据具体的产品族进行设计,因此抽象工厂类的设计难度较大。
- 产品族的扩展较为困难:当需要添加新的产品族时,需要创建新的具体工厂类,并且需要在抽象工厂类中添加相应的抽象方法,这可能会导致抽象工厂类的代码变得复杂。
- 客户端代码的修改较为困难:当需要修改产品族的实现时,需要修改具体工厂类的代码,这可能会导致客户端代码的修改较为困难。
六、适用场景
- 当需要创建的对象具有复杂的结构,且需要分步骤创建时,可以使用生成器模式。
- 当需要创建的对象具有不同的实现方式,且这些实现方式之间存在差异时,可以使用生成器模式。
- 当需要创建的对象具有不同的配置选项,且这些配置选项之间存在差异时,可以使用生成器模式。
七、总结
工厂模式与抽象工厂模式的区别在于:
工厂方法模式:针对的是 一个产品等级结构。 抽象工厂模式:针对 多个产品等级结构
抽象工厂模式是一种创建型设计模式,它能创建一系列相关的对象,而无需指定其具体类。抽象工厂模式将产品的创建过程封装在抽象工厂类中,使得客户端代码无需关心产品的具体创建过程,只需要通过抽象工厂类提供的方法来获取产品即可。抽象工厂模式支持产品族的扩展,当需要添加新的产品族时,只需要创建新的具体工厂类即可,无需修改现有代码。抽象工厂模式可以根据不同的需求创建不同的产品族,从而提高了系统的灵活性。
生成器模式
一、定义
生成器模式是一种创建型设计模式,它使你能够分步骤创建复杂对象。该模式允许你使用相同的创建代码生成不同类型和形式的对象。
二、结构
- 生成器(Builder):声明了创建产品对象的抽象接口,通常包含一系列创建方法。
- 具体生成器(Concrete Builder):实现了生成器接口,负责创建具体的产品对象,并提供了设置产品参数的方法。
- 产品(Product):表示被创建的复杂对象,包含多个部件。
- 主管(Director):负责使用生成器对象创建产品,它知道如何按照一定的顺序调用生成器的方法来创建产品。
三、示例代码(用 C 语言举汽车例子)
#include <stdio.h>
#include <stdlib.h>
// 定义汽车类型枚举
typedef enum {
SEDAN,
SUV,
HATCHBACK
} CarType;
// 汽车结构体
typedef struct {
char* model;
int year;
CarType type;
} Car;
// 声明生成器
struct Builder;
// 生成器接口
typedef struct Builder {
void (*setModel)(struct Builder*, char*);
void (*setYear)(struct Builder*, int);
void (*setType)(struct Builder*, CarType);
Car* (*build)(struct Builder*);
} Builder;
// 具体生成器
typedef struct {
// base 用于模拟继承,ConcreteBuilder* concreteBuilder = (ConcreteBuilder*)builder;
Builder base;
Car* car;
} ConcreteBuilder;
// 设置汽车模型
void setModel(Builder* builder, char* model) {
ConcreteBuilder* concreteBuilder = (ConcreteBuilder*)builder;
concreteBuilder->car->model = model;
}
// 设置汽车年份
void setYear(Builder* builder, int year) {
ConcreteBuilder* concreteBuilder = (ConcreteBuilder*)builder;
concreteBuilder->car->year = year;
}
// 设置汽车类型
void setType(Builder* builder, CarType type) {
ConcreteBuilder* concreteBuilder = (ConcreteBuilder*)builder;
concreteBuilder->car->type = type;
}
// 构建汽车
Car* build(Builder* builder) {
ConcreteBuilder* concreteBuilder = (ConcreteBuilder*)builder;
return concreteBuilder->car;
}
// 主管
typedef struct {
Builder* builder;
} Director;
// 创建主管
Director* createDirector(Builder* builder) {
Director* director = (Director*)malloc(sizeof(Director));
director->builder = builder;
return director;
}
// 制造汽车
Car* manufactureCar(Director* director) {
director->builder->setModel(director->builder, "Toyota Camry");
director->builder->setYear(director->builder, 2023);
director->builder->setType(director->builder, SEDAN);
return director->builder->build(director->builder);
}
int main() {
// 创建具体生成器
ConcreteBuilder* concreteBuilder = (ConcreteBuilder*)malloc(sizeof(ConcreteBuilder));
concreteBuilder->car = (Car*)malloc(sizeof(Car));
// 初始化生成器接口
concreteBuilder->base.setModel = setModel;
concreteBuilder->base.setYear = setYear;
concreteBuilder->base.setType = setType;
concreteBuilder->base.build = build;
// 创建主管
Director* director = createDirector((Builder*)concreteBuilder);
// 制造汽车
Car* car = manufactureCar(director);
// 输出汽车信息
printf("Model: %s\n", car->model);
printf("Year: %d\n", car->year);
printf("Type: %d\n", car->type);
// 释放内存
free(car);
free(concreteBuilder);
free(director);
return 0;
}
在上述示例中,我们定义了一个汽车结构体Car
,它包含汽车的模型、年份和类型等信息。然后,我们定义了一个生成器接口Builder
,它包含了设置汽车模型、年份和类型的方法,以及构建汽车的方法。接着,我们定义了一个具体生成器ConcreteBuilder
,它实现了生成器接口,并在构建汽车时创建了一个汽车结构体的实例。
为了使用生成器模式创建汽车,我们还定义了一个主管Director
,它包含了一个生成器实例。主管的manufactureCar
方法按照一定的顺序调用生成器的方法来创建汽车。
在main
函数中,我们首先创建了一个具体生成器和一个生成器实例,然后创建了一个主管。接着,我们使用主管的manufactureCar
方法创建了一辆汽车,并输出了汽车的信息。最后,我们释放了所有的内存。
四、优点
- 封装性:生成器模式将产品的创建过程封装在生成器类中,使得客户端代码无需关心产品的具体创建过程,只需要通过生成器类提供的方法来获取产品即可。
- 灵活性:生成器模式可以根据不同的需求创建不同的生成器类,从而实现不同的产品创建过程。
- 可扩展性:生成器模式可以很容易地添加新的产品类型和创建过程,只需要创建新的生成器类即可。
五、缺点
- 复杂性:生成器模式需要定义多个类来实现产品的创建过程,因此代码结构比较复杂。
- 性能问题:生成器模式在创建产品时需要进行多次方法调用,因此可能会影响代码的性能。
六、适用场景
- 当需要创建的对象具有复杂的结构,且需要分步骤创建时,可以使用生成器模式。
- 当需要创建的对象具有不同的实现方式,且这些实现方式之间存在差异时,可以使用生成器模式。
- 当需要创建的对象具有不同的配置选项,且这些配置选项之间存在差异时,可以使用生成器模式。
七、总结
生成器模式是一种创建型设计模式,它使你能够分步骤创建复杂对象。该模式允许你使用相同的创建代码生成不同类型和形式的对象。生成器模式的优点是封装性、灵活性和可扩展性,缺点是复杂性和性能问题。生成器模式适用于需要创建的对象具有复杂的结构、不同的实现方式或不同的配置选项的场景。
原型模式
一、定义
原型模式是一种创建型设计模式,它使你能够复制已有对象,而又无需使代码依赖它们所属的类。
二、结构
- 原型(Prototype):声明了克隆方法的接口。在绝大多数情况下,其中只会有一个名为
clone
的方法。 - 具体原型(Concrete Prototype):实现了克隆方法的类。除了将原始对象的数据复制到克隆体中之外,该方法有时还需处理克隆过程中的极端情况,例如克隆关联对象和梳理递归依赖等等。
- 客户端(Client):可以复制实现了原型接口的任何对象。
三、示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 定义汽车类型枚举
typedef enum {
SEDAN,
SUV,
HATCHBACK
} CarType;
// 汽车结构体
typedef struct Car {
char* model;
int year;
CarType type;
struct Car* (*clone)(struct Car*);
} Car;
// 克隆函数
Car* cloneCar(Car* original) {
Car* newCar = (Car*)malloc(sizeof(Car));
newCar->model = strdup(original->model);
newCar->year = original->year;
newCar->type = original->type;
newCar->clone = original->clone;
return newCar;
}
// 创建新的汽车实例
Car* createCar(char* model, int year, CarType type) {
Car* car = (Car*)malloc(sizeof(Car));
car->model = strdup(model);
car->year = year;
car->type = type;
car->clone = cloneCar;
return car;
}
// 打印汽车信息
void printCar(Car* car) {
printf("Model: %s\n", car->model);
printf("Year: %d\n", car->year);
printf("Type: %d\n", car->type);
}
int main() {
// 创建原型汽车
Car* prototypeCar = createCar("Toyota Camry", 2023, SEDAN);
// 克隆新汽车
Car* clonedCar = prototypeCar->clone(prototypeCar);
// 修改克隆汽车的信息
clonedCar->model = strdup("Honda Accord");
clonedCar->year = 2024;
// 打印汽车信息
printf("Original Car:\n");
printCar(prototypeCar);
printf("\nCloned Car:\n");
printCar(clonedCar);
// 释放内存
free(prototypeCar->model);
free(prototypeCar);
free(clonedCar->model);
free(clonedCar);
return 0;
}
四、优点
- 提高性能:原型模式可以避免重复创建对象的开销,特别是在创建复杂对象时。
- 方便创建复杂对象:原型模式可以通过复制已有对象来创建新对象,而无需关心对象的具体实现细节。
- 便于扩展:原型模式可以通过添加新的原型类来扩展系统的功能,而无需修改已有代码。
五、缺点
- 内存消耗:原型模式需要为每个原型对象分配内存,如果原型对象数量较多,可能会导致内存消耗过大。
- 深拷贝问题:如果原型对象包含指向其他对象的引用,那么在克隆原型对象时,需要进行深拷贝,否则可能会导致引用的对象被多个克隆对象共享,从而引发问题。
- 安全问题:原型模式可能会导致安全问题,例如,如果原型对象包含敏感信息,那么在克隆原型对象时,需要进行特殊处理,以避免敏感信息泄露。
六、适用场景
- 创建复杂对象:如果创建一个复杂对象的过程比较复杂,或者需要大量的计算资源,那么可以使用原型模式来提高创建对象的效率。
- 需要频繁创建对象:如果需要频繁创建对象,那么可以使用原型模式来避免重复创建对象的开销。
- 对象状态变化较小:如果对象的状态变化较小,那么可以使用原型模式来减少对象的创建和销毁次数。
七、总结
原型模式是一种创建型设计模式,它通过复制已有对象来创建新对象,从而避免了重复创建对象的开销。原型模式的优点是提高性能、方便创建复杂对象和便于扩展,缺点是内存消耗、深拷贝问题和安全问题。原型模式适用于创建复杂对象、需要频繁创建对象和对象状态变化较小的场景。
单例模式
一、定义
单例模式是一种创建型设计模式,它保证一个类只有一个实例,并提供一个全局访问点来访问该实例。
二、结构
- 单例类:包含一个私有构造函数,以防止外部创建实例。它还包含一个静态成员变量来存储唯一的实例,并提供一个公共静态方法来获取该实例。
- 客户端:通过调用单例类的公共静态方法来获取唯一的实例。
三、示例代码(用 C 语言举汽车例子)
#include <stdio.h>
#include <stdlib.h>
// 定义汽车类型枚举
typedef enum {
SEDAN,
SUV,
HATCHBACK
} CarType;
// 汽车结构体
typedef struct {
char* model;
int year;
CarType type;
} Car;
// 单例类
typedef struct {
Car* car;
} Singleton;
// 获取单例实例的方法
Singleton* getSingleton() {
static Singleton instance = {NULL};
if (instance.car == NULL) {
instance.car = (Car*)malloc(sizeof(Car));
instance.car->model = "Toyota Camry";
instance.car->year = 2023;
instance.car->type = SEDAN;
}
return &instance;
}
int main() {
// 获取单例实例
Singleton* singleton = getSingleton();
// 打印汽车信息
printf("Model: %s\\n", singleton->car->model);
printf("Year: %d\\n", singleton->car->year);
printf("Type: %d\\n", singleton->car->type);
// 释放内存
free(singleton->car);
return 0;
}
在上述示例中,我们定义了一个Car
结构体来表示汽车的属性,包括车型、生产年份和车辆类型。然后,我们定义了一个Singleton
结构体来表示单例类,其中包含一个Car
类型的指针car
,用于存储唯一的汽车实例。
在getSingleton
方法中,我们使用静态变量instance
来存储单例实例。如果instance
为空,则创建一个新的汽车实例并将其赋值给instance
。如果instance
已经存在,则直接返回该实例。
在main
函数中,我们调用getSingleton
方法获取单例实例,并打印汽车的信息。最后,我们释放单例实例所占用的内存。
四、优点
- 保证唯一性:单例模式保证一个类只有一个实例,避免了多个实例之间的竞争和冲突。
- 全局访问点:单例模式提供了一个全局访问点来访问唯一的实例,方便了代码的使用和管理。
- 节省资源:单例模式只创建一个实例,避免了多次创建和销毁实例所带来的资源浪费。
- 提高性能:单例模式避免了多个实例之间的竞争和冲突,提高了程序的性能。
五、缺点
- 违反单一职责原则:单例模式将创建实例和管理实例的职责集中在一个类中,违反了单一职责原则。
- 难以测试:单例模式的实例是在程序启动时创建的,难以在测试中进行模拟和控制。
- 可能导致内存泄漏:如果单例模式的实例没有被正确释放,可能会导致内存泄漏。
- 不支持多线程:在多线程环境下,单例模式可能会出现线程安全问题,需要进行特殊处理。
六、适用场景
- 需要全局访问的对象:如果一个对象需要在整个程序中被全局访问,例如日志记录器、数据库连接池等,可以使用单例模式来保证只有一个实例存在。
- 资源共享的对象:如果一个对象需要被多个线程或进程共享,例如文件系统、网络连接等,可以使用单例模式来保证只有一个实例存在,避免资源竞争和冲突。
- 需要频繁创建和销毁的对象:如果一个对象的创建和销毁非常频繁,例如线程池、对象池等,可以使用单例模式来避免频繁的创建和销毁操作,提高程序的性能。
- 需要保证唯一性的对象:如果一个对象需要保证唯一性,例如序列号生成器、唯一标识符生成器等,可以使用单例模式来保证只有一个实例存在,避免重复生成。
七、总结
单例模式是一种简单而实用的设计模式,它保证了一个类只有一个实例,并提供了一个全局访问点来访问该实例。单例模式的优点是保证唯一性、全局访问点、节省资源和提高性能,缺点是违反单一职责原则、难以测试、可能导致内存泄漏和不支持多线程。单例模式适用于需要全局访问的对象、资源共享的对象、需要频繁创建和销毁的对象和需要保证唯一性的对象。在使用单例模式时,需要注意处理好线程安全问题,避免出现内存泄漏等问题。
五、结构型模式
适配器模式
一、定义
适配器模式是一种结构型设计模式,它能使接口不兼容的对象能够相互合作。
二、结构
- 客户端(Client):使用适配器的对象。
- 目标接口(Target):客户端期望的接口。
- 适配器(Adapter):将源接口转换为目标接口的对象。
- 源接口(Adaptee):需要被适配的接口。
三、示例代码(用 C 语言举汽车例子)
#include <stdio.h>
// 定义汽车类型枚举
typedef enum {
SEDAN,
SUV,
HATCHBACK
} CarType;
// 汽车结构体
typedef struct {
char* model;
int year;
CarType type;
} Car;
// 电动汽车结构体
typedef struct {
Car car;
int batteryCapacity;
} ElectricCar;
// 定义电动汽车接口
typedef struct {
void (*charge)(struct ElectricCar*);
} ElectricCarInterface;
// 实现电动汽车接口的充电方法
void charge(ElectricCar* electricCar) {
printf("Charging the electric car: %s\\n", electricCar->car.model);
}
// 定义传统燃油汽车接口
typedef struct {
void (*refuel)(struct GasolineCar*);
} GasolineCarInterface;
// 传统燃油汽车结构体
typedef struct {
Car car;
int fuelCapacity;
} GasolineCar;
// 实现传统燃油汽车接口的加油方法
void refuel(GasolineCar* gasolineCar) {
printf("Refueling the gasoline car: %s\n", gasolineCar->car.model);
}
// 定义适配器结构体
typedef struct {
ElectricCarInterface electricCarInterface;
GasolineCar* gasolineCar;
} Adapter;
// 实现适配器的充电方法
void adapterCharge(Adapter* adapter) {
refuel(adapter->gasolineCar);
}
int main() {
// 创建传统燃油汽车对象
GasolineCar gasolineCar = {
.car = {
.model = "Toyota Camry",
.year = 2023,
.type = SEDAN
},
.fuelCapacity = 50
};
// 创建适配器对象,并将传统燃油汽车对象作为参数传递给适配器的构造函数
Adapter adapter = {
.electricCarInterface = {
.charge = adapterCharge
},
.gasolineCar = &gasolineCar
};
// 通过适配器对象调用充电方法
adapter.electricCarInterface.charge(&adapter);
return 0;
}
在上述示例中,我们定义了一个汽车类型枚举CarType
,表示汽车的类型。然后,我们定义了一个汽车结构体Car
,表示汽车的基本信息。
接下来,我们定义了一个电动汽车接口ElectricCarInterface
,其中包含一个充电方法charge
。我们还定义了一个电动汽车结构体ElectricCar
,它包含一个汽车结构体Car
和一个电池容量batteryCapacity
。
然后,我们定义了一个传统燃油汽车接口GasolineCarInterface
,其中包含一个加油方法refuel
。我们还定义了一个传统燃油汽车结构体GasolineCar
,它包含一个汽车结构体Car
和一个燃油容量fuelCapacity
。
为了使传统燃油汽车能够使用电动汽车的充电接口,我们定义了一个适配器结构体Adapter
,它包含一个电动汽车接口ElectricCarInterface
和一个传统燃油汽车结构体GasolineCar
的指针。
在适配器的构造函数中,我们将传统燃油汽车结构体的指针赋值给适配器结构体中的gasolineCar
指针。
在适配器的充电方法adapterCharge
中,我们调用了传统燃油汽车结构体的加油方法refuel
,实现了将传统燃油汽车的加油接口转换为电动汽车的充电接口的功能。
在main
函数中,我们创建了一个传统燃油汽车对象gasolineCar
,并创建了一个适配器对象adapter
,将传统燃油汽车对象作为参数传递给适配器的构造函数。
最后,我们通过适配器对象调用充电方法charge
,实现了对传统燃油汽车的充电操作。
四、优点
- 提高了代码的复用性:适配器模式可以将现有的接口转换为客户端期望的接口,从而提高了代码的复用性。
- 提高了代码的灵活性:适配器模式可以在不修改现有代码的情况下,将现有的接口转换为客户端期望的接口,从而提高了代码的灵活性。
- 提高了代码的可扩展性:适配器模式可以在不修改现有代码的情况下,将现有的接口转换为客户端期望的接口,从而提高了代码的可扩展性。
五、缺点
- 增加了代码的复杂度:适配器模式需要定义一个适配器类,将现有的接口转换为客户端期望的接口,从而增加了代码的复杂度。
- 降低了代码的性能:适配器模式需要将现有的接口转换为客户端期望的接口,从而降低了代码的性能。
六、适用场景
- 需要将现有的接口转换为客户端期望的接口:当现有的接口与客户端期望的接口不兼容时,可以使用适配器模式将现有的接口转换为客户端期望的接口。
- 需要提高代码的复用性和灵活性:当需要提高代码的复用性和灵活性时,可以使用适配器模式将现有的接口转换为客户端期望的接口。
- 需要提高代码的可扩展性:当需要提高代码的可扩展性时,可以使用适配器模式将现有的接口转换为客户端期望的接口。
七、总结
适配器模式是一种结构型设计模式,它能使接口不兼容的对象能够相互合作。适配器模式的优点是提高了代码的复用性、灵活性和可扩展性,缺点是增加了代码的复杂度和降低了代码的性能。适配器模式适用于需要将现有的接口转换为客户端期望的接口的场景。
桥接模式
- 一、定义
桥接模式是一种结构型设计模式,它通过将抽象部分与实现部分分离,使它们可以独立变化。它是通过组合而不是继承来达到目的,即将抽象和实现用组合的方式桥接在一起。
- 二、结构
桥接模式主要包括以下几个部分:
- 抽象部分(Abstraction):定义高层接口,并维护对实现部分的引用。
- 实现部分(Implementor):定义实现接口,但不提供具体实现。
- 具体实现部分(ConcreteImplementor):实现实现接口的具体类。
- 扩展抽象部分(RefinedAbstraction):扩展抽象部分的接口,通常是高层业务逻辑。
- 三、示例代码
假设我们要设计一个汽车应用程序,不同类型的汽车(如轿车、SUV)可以有不同的操作系统(如Android Auto、Apple CarPlay)。我们使用桥接模式来实现这一点。
#include <stdio.h>
#include <stdlib.h>
// 实现部分接口
typedef struct CarSystem {
void (*playMusic)(struct CarSystem*);
void (*navigate)(struct CarSystem*);
} CarSystem;
// 具体实现部分:Android Auto
typedef struct AndroidAuto {
CarSystem carSystem;
} AndroidAuto;
void androidPlayMusic(CarSystem* carSystem) {
printf("Playing music with Android Auto.\\n");
}
void androidNavigate(CarSystem* carSystem) {
printf("Navigating with Android Auto.\\n");
}
AndroidAuto* createAndroidAuto() {
AndroidAuto* androidAuto = (AndroidAuto*)malloc(sizeof(AndroidAuto));
androidAuto->carSystem.playMusic = androidPlayMusic;
androidAuto->carSystem.navigate = androidNavigate;
return androidAuto;
}
// 具体实现部分:Apple CarPlay
typedef struct AppleCarPlay {
CarSystem carSystem;
} AppleCarPlay;
void applePlayMusic(CarSystem* carSystem) {
printf("Playing music with Apple CarPlay.\\n");
}
void appleNavigate(CarSystem* carSystem) {
printf("Navigating with Apple CarPlay.\\n");
}
AppleCarPlay* createAppleCarPlay() {
AppleCarPlay* appleCarPlay = (AppleCarPlay*)malloc(sizeof(AppleCarPlay));
appleCarPlay->carSystem.playMusic = applePlayMusic;
appleCarPlay->carSystem.navigate = appleNavigate;
return appleCarPlay;
}
// 抽象部分:汽车
typedef struct Car {
CarSystem* carSystem;
void (*playMusic)(struct Car*);
void (*navigate)(struct Car*);
} Car;
void carPlayMusic(Car* car) {
car->carSystem->playMusic(car->carSystem);
}
void carNavigate(Car* car) {
car->carSystem->navigate(car->carSystem);
}
Car* createCar(CarSystem* carSystem) {
Car* car = (Car*)malloc(sizeof(Car));
car->carSystem = carSystem;
car->playMusic = carPlayMusic;
car->navigate = carNavigate;
return car;
}
// 扩展抽象部分:轿车
typedef struct Sedan {
Car car;
} Sedan;
Sedan* createSedan(CarSystem* carSystem) {
Sedan* sedan = (Sedan*)malloc(sizeof(Sedan));
sedan->car.carSystem = carSystem;
sedan->car.playMusic = carPlayMusic;
sedan->car.navigate = carNavigate;
return sedan;
}
// 扩展抽象部分:SUV
typedef struct SUV {
Car car;
} SUV;
SUV* createSUV(CarSystem* carSystem) {
SUV* suv = (SUV*)malloc(sizeof(SUV));
suv->car.carSystem = carSystem;
suv->car.playMusic = carPlayMusic;
suv->car.navigate = carNavigate;
return suv;
}
int main() {
// 创建具体实现部分对象
AndroidAuto* androidAuto = createAndroidAuto();
AppleCarPlay* appleCarPlay = createAppleCarPlay();
// 创建扩展抽象部分对象,使用不同的实现部分
Sedan* sedanWithAndroid = createSedan((CarSystem*)androidAuto);
SUV* suvWithApple = createSUV((CarSystem*)appleCarPlay);
// 使用扩展抽象部分对象
sedanWithAndroid->car.playMusic((Car*)sedanWithAndroid);
sedanWithAndroid->car.navigate((Car*)sedanWithAndroid);
suvWithApple->car.playMusic((Car*)suvWithApple);
suvWithApple->car.navigate((Car*)suvWithApple);
// 清理内存
free(androidAuto);
free(appleCarPlay);
free(sedanWithAndroid);
free(suvWithApple);
return 0;
}
- 主要结构说明
- 实现部分(Implementor):定义了
CarSystem
接口,包括playMusic
和navigate
方法。 - 具体实现部分(ConcreteImplementor):定义了
AndroidAuto
和AppleCarPlay
,分别实现了CarSystem
接口的方法。 - 抽象部分(Abstraction):定义了
Car
,包含一个指向CarSystem
的指针,并提供playMusic
和navigate
方法,这些方法将调用CarSystem
的相应方法。 - 扩展抽象部分(RefinedAbstraction):定义了
Sedan
和SUV
,它们扩展了Car
。
- 四、优点
- 分离抽象和实现:可以独立地扩展抽象部分和实现部分。
- 提高系统可扩展性:桥接模式提高了系统的可扩展性。
- 降低类的数量:通过组合来减少继承层次,可以有效地减少类的数量。
- 五、缺点
- 增加复杂性:引入了更多的类和接口,增加了系统的复杂性。
- 六、适用场景
- 需要在抽象和具体实现之间增加更多的灵活性。
- 不希望使用继承或层次过深的继承结构。
- 一个类存在两个独立变化的维度,并且这两个维度都需要独立扩展。
- 七、总结
桥接模式通过分离抽象部分和实现部分,使得它们可以独立变化,提高了系统的可扩展性和灵活性。在汽车应用中,不同类型的汽车可以使用不同的操作系统,这展示了桥接模式如何在实际项目中使用。
组合模式
一、定义
组合模式是一种结构型设计模式,它允许你将对象组合成树状结构来表示“部分-整体”的层次结构。组合模式使得客户端可以统一对待单个对象和组合对象。
二、结构
组合模式主要包括以下几个部分:
- 组件(Component):定义对象的接口,并提供接口的默认实现。
- 叶子(Leaf):表示叶节点对象,没有子节点。
- 容器(Composite):包含子节点,既可以是叶子也可以是其他容器。
三、示例代码
假设我们要设计一个汽车应用程序,不同的汽车零部件(如车轮、发动机)可以组合成一个整体汽车。我们使用组合模式来实现这一点。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 定义组件接口
typedef struct Component {
void (*operation)(struct Component*);
void (*add)(struct Component*, struct Component*);
void (*remove)(struct Component*, struct Component*);
struct Component* (*getChild)(struct Component*, int);
char* name;
} Component;
// 定义叶子节点:汽车零部件
typedef struct Leaf {
Component component;
} Leaf;
void leafOperation(Component* component) {
printf("Leaf: %s\\n", component->name);
}
void leafAdd(Component* component, Component* child) {
// 叶子节点不能添加子节点
}
void leafRemove(Component* component, Component* child) {
// 叶子节点不能移除子节点
}
Component* leafGetChild(Component* component, int index) {
return NULL; // 叶子节点没有子节点
}
Leaf* createLeaf(const char* name) {
Leaf* leaf = (Leaf*)malloc(sizeof(Leaf));
leaf->component.name = strdup(name);
leaf->component.operation = leafOperation;
leaf->component.add = leafAdd;
leaf->component.remove = leafRemove;
leaf->component.getChild = leafGetChild;
return leaf;
}
// 定义组合节点:汽车
typedef struct Composite {
Component component;
Component** children;
int childCount;
int capacity;
} Composite;
void compositeOperation(Component* component) {
Composite* composite = (Composite*)component;
printf("Composite: %s\\n", component->name);
for (int i = 0; i < composite->childCount; ++i) {
composite->children[i]->operation(composite->children[i]);
}
}
void compositeAdd(Component* component, Component* child) {
Composite* composite = (Composite*)component;
if (composite->childCount >= composite->capacity) {
composite->capacity *= 2;
composite->children = (Component**)realloc(composite->children, composite->capacity * sizeof(Component*));
}
composite->children[composite->childCount++] = child;
}
void compositeRemove(Component* component, Component* child) {
Composite* composite = (Composite*)component;
for (int i = 0; i < composite->childCount; ++i) {
if (composite->children[i] == child) {
for (int j = i; j < composite->childCount - 1; ++j) {
composite->children[j] = composite->children[j + 1];
}
composite->childCount--;
break;
}
}
}
Component* compositeGetChild(Component* component, int index) {
Composite* composite = (Composite*)component;
if (index < 0 || index >= composite->childCount) {
return NULL;
}
return composite->children[index];
}
Composite* createComposite(const char* name) {
Composite* composite = (Composite*)malloc(sizeof(Composite));
composite->component.name = strdup(name);
composite->component.operation = compositeOperation;
composite->component.add = compositeAdd;
composite->component.remove = compositeRemove;
composite->component.getChild = compositeGetChild;
composite->children = (Component**)malloc(4 * sizeof(Component*));
composite->capacity = 4;
composite->childCount = 0;
return composite;
}
// 测试组合模式
int main() {
// 创建叶子节点:汽车零部件
Leaf* wheel = createLeaf("Wheel");
Leaf* engine = createLeaf("Engine");
Leaf* door = createLeaf("Door");
// 创建组合节点:汽车
Composite* car = createComposite("Car");
// 将零部件添加到汽车
car->component.add((Component*)car, (Component*)wheel);
car->component.add((Component*)car, (Component*)engine);
car->component.add((Component*)car, (Component*)door);
// 调用汽车的操作,应该递归调用子节点的操作
car->component.operation((Component*)car);
// 清理内存
free(wheel->component.name);
free(wheel);
free(engine->component.name);
free(engine);
free(door->component.name);
free(door);
free(car->children);
free(car->component.name);
free(car);
return 0;
}
主要结构说明
- 组件(Component):定义了统一的接口,包含操作方法
operation
以及管理子节点的方法add
、remove
和getChild
。还包含一个名称属性name
。 - 叶子(Leaf):具体实现了组件接口,没有子节点的功能。实现了
operation
方法。 - 容器(Composite):实现了组件接口,并包含一个子节点数组,用于管理其子节点。实现了
operation
、add
、remove
和getChild
方法。 - 测试组合模式:创建了多个叶子节点(汽车零部件)和一个组合节点(汽车),并将叶子节点添加到组合节点中,最终调用组合节点的操作方法来展示组合模式的工作方式。
四、优点
- 清晰的层次结构:组合模式能清晰地表示对象的层次结构。
- 客户端一致性:客户端可以一致地对待单个对象和组合对象。
- 便于扩展:可以很容易地增加新的叶子节点或组合节点。
五、缺点
- 类型安全性较低:由于使用了通用的组件接口,类型检查只能在运行时进行。
- 管理复杂性:如果层次结构较深,可能会导致管理复杂性增加。
六、适用场景
- 需要表示对象的部分-整体层次结构。
- 希望客户端可以统一处理单个对象和组合对象。
七、 总结
组合模式通过定义统一的接口,使得单个对象和组合对象可以一致地进行处理。它适用于需要表示部分-整体层次结构的场景。在汽车应用中,汽车可以由不同的零部件组合而成,展示了组合模式如何在实际项目中使用。
装饰模式
一、定义
装饰模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。它是一种结构型设计模式,通过创建一个装饰类来包装原有的类,并在不修改原类的情况下添加额外的行为。
二、结构
装饰模式的主要角色包括:
- 组件(Component):定义了一个对象接口,可以给这些对象动态地添加职责。
- 具体组件(Concrete Component):定义了一个具体的对象,也可以给这个对象添加一些职责。
- 装饰(Decorator):装饰抽象类,继承了Component,从外类来扩展Component类的功能,但对于Component来说,是无需知道Decorator的存在的。
- 具体装饰(Concrete Decorator):具体的装饰对象,起到给Component添加职责的功能。
三、示例代码
下面是一个使用装饰模式的示例代码,演示了如何使用装饰模式来为一个汽车添加不同的功能:
#include <stdio.h>
// 定义汽车结构体
typedef struct {
char *name;
int speed;
} Car;
// 定义汽车装饰结构体
typedef struct {
Car *car;
void (*decorate)(struct Decorator *decorator);
} Decorator;
// 定义汽车装饰函数
void decorateWithGPS(Decorator *decorator) {
printf("%s 汽车已安装 GPS\\n", decorator->car->name);
}
void decorateWithSunroof(Decorator *decorator) {
printf("%s 汽车已安装天窗\\n", decorator->car->name);
}
// 创建汽车对象
Car *createCar(char *name, int speed) {
Car *car = (Car *)malloc(sizeof(Car));
car->name = name;
car->speed = speed;
return car;
}
// 创建汽车装饰对象
Decorator *createDecorator(Car *car, void (*decorate)(Decorator *decorator)) {
Decorator *decorator = (Decorator *)malloc(sizeof(Decorator));
decorator->car = car;
decorator->decorate = decorate;
return decorator;
}
// 测试函数
int main() {
// 创建汽车对象
Car *car = createCar("宝马", 200);
// 创建汽车装饰对象
Decorator *decoratorWithGPS = createDecorator(car, decorateWithGPS);
Decorator *decoratorWithSunroof = createDecorator(car, decorateWithSunroof);
// 调用汽车装饰函数
decoratorWithGPS->decorate(decoratorWithGPS);
decoratorWithSunroof->decorate(decoratorWithSunroof);
// 释放内存
free(car);
free(decoratorWithGPS);
free(decoratorWithSunroof);
return 0;
}
在上述代码中,我们首先定义了一个Car
结构体,其中包含汽车的名称和速度。然后,我们定义了一个Decorator
结构体,其中包含一个指向Car
结构体的指针和一个装饰函数指针。接着,我们定义了两个装饰函数decorateWithGPS
和decorateWithSunroof
,分别用于为汽车添加GPS和天窗功能。然后,我们使用createCar
函数创建了一个汽车对象,并使用createDecorator
函数为汽车对象创建了两个装饰对象,分别用于添加GPS和天窗功能。最后,我们使用decorate
函数调用了装饰对象的装饰函数,为汽车添加了GPS和天窗功能。
四、优点
- 动态扩展:装饰模式可以在不改变原有对象结构的情况下,动态地给对象添加新的功能,具有很强的灵活性。
- 遵循开闭原则:装饰模式可以在不修改原有代码的情况下,扩展对象的功能,符合开闭原则。
- 简化代码:装饰模式可以将复杂的功能分解为多个简单的装饰类,每个装饰类只负责一个特定的功能,从而简化了代码的结构。
- 提高代码的可维护性:装饰模式将功能分解为多个独立的装饰类,每个装饰类只负责一个特定的功能,从而提高了代码的可维护性。
五、缺点
- 增加了系统的复杂性:装饰模式需要创建多个装饰类,增加了系统的复杂性。
- 降低了系统的性能:装饰模式需要创建多个装饰类,并且在运行时需要动态地选择装饰类,从而降低了系统的性能。
六、适用场景
- 需要动态地给对象添加新的功能:装饰模式可以在不改变原有对象结构的情况下,动态地给对象添加新的功能,具有很强的灵活性。
- 需要在不修改原有代码的情况下,扩展对象的功能:装饰模式可以在不修改原有代码的情况下,扩展对象的功能,符合开闭原则。
- 需要将复杂的功能分解为多个简单的装饰类:装饰模式可以将复杂的功能分解为多个简单的装饰类,每个装饰类只负责一个特定的功能,从而简化了代码的结构,提高了代码的可维护性。
七、总结
装饰模式是一种非常有用的设计模式,它可以在不改变原有对象结构的情况下,动态地给对象添加新的功能,具有很强的灵活性。装饰模式遵循开闭原则,可以在不修改原有代码的情况下,扩展对象的功能。装饰模式可以将复杂的功能分解为多个简单的装饰类,每个装饰类只负责一个特定的功能,从而简化了代码的结构,提高了代码的可维护性。
外观模式
一、定义
外观模式是一种结构型设计模式,它为子系统中的一组接口提供一个统一的高层接口,使得子系统更容易使用。
二、结构
- 外观类(Facade):外观类是外观模式的核心,它为子系统中的一组接口提供一个统一的高层接口,使得子系统更容易使用。外观类通常包含一些与子系统相关的业务逻辑,它可以将这些业务逻辑封装起来,对外提供一个简单的接口。
- 子系统类(Subsystem Classes):子系统类是外观模式的组成部分,它们是子系统中的具体实现类,负责实现子系统的具体功能。子系统类通常包含一些与子系统相关的业务逻辑,它们可以将这些业务逻辑封装起来,对外提供一个简单的接口。
三、示例代码(C语言汽车举例)
#include <stdio.h>
#include <stdlib.h>
// 定义汽车类型枚举
typedef enum {
SEDAN,
SUV,
HATCHBACK
} CarType;
// 定义汽车结构体
typedef struct {
char* model;
int year;
CarType type;
} Car;
// 创建汽车对象
Car* createCar(CarType type) {
Car* car = (Car*)malloc(sizeof(Car));
car->model = "Toyota Camry";
car->year = 2023;
car->type = type;
return car;
}
// 定义汽车工厂结构体
typedef struct {
Car* (*createCar)(CarType type);
} CarFactory;
// 创建汽车工厂对象
CarFactory* createCarFactory() {
CarFactory* factory = (CarFactory*)malloc(sizeof(CarFactory));
factory->createCar = createCar;
return factory;
}
// 定义汽车经销商结构体
typedef struct {
CarFactory* factory;
} CarDealer;
// 创建汽车经销商对象
CarDealer* createCarDealer() {
CarDealer* dealer = (CarDealer*)malloc(sizeof(CarDealer));
dealer->factory = createCarFactory();
return dealer;
}
// 销售汽车
void sellCar(CarDealer* dealer, CarType type) {
Car* car = dealer->factory->createCar(type);
printf("Sold a %s %d\\n", car->model, car->year);
free(car);
}
int main() {
// 创建汽车经销商对象
CarDealer* dealer = createCarDealer();
// 销售汽车
sellCar(dealer, SEDAN);
sellCar(dealer, SUV);
sellCar(dealer, HATCHBACK);
// 释放汽车经销商对象
free(dealer);
return 0;
}
在上述示例代码中,我们定义了一个汽车工厂结构体CarFactory
,它包含一个创建汽车对象的函数指针createCar
。我们还定义了一个汽车经销商结构体CarDealer
,它包含一个汽车工厂对象factory
。在main
函数中,我们创建了一个汽车经销商对象dealer
,并通过调用sellCar
函数来销售汽车。在sellCar
函数中,我们通过调用汽车工厂对象的createCar
函数来创建汽车对象,并将其销售出去。最后,我们释放了汽车经销商对象。
四、优点
- 简化了客户端的代码:外观模式为子系统中的一组接口提供了一个统一的高层接口,使得客户端的代码更加简洁。客户端只需要与外观类进行交互,而不需要与子系统中的具体实现类进行交互,从而减少了客户端的代码量。
- 提高了系统的可维护性:外观模式将子系统中的具体实现类隐藏起来,使得客户端只需要与外观类进行交互,而不需要了解子系统的具体实现细节。这样,当子系统中的具体实现类发生变化时,客户端的代码不需要进行修改,从而提高了系统的可维护性。
- 提高了系统的灵活性:外观模式可以根据客户端的需求,动态地选择不同的子系统实现类,从而提高了系统的灵活性。例如,当客户端需要使用不同的汽车品牌时,只需要修改外观类中的汽车工厂对象,而不需要修改客户端的代码。
五、缺点
- 可能会增加系统的复杂度:外观模式将子系统中的具体实现类隐藏起来,使得客户端只需要与外观类进行交互,而不需要了解子系统的具体实现细节。这样,当子系统中的具体实现类发生变化时,客户端的代码不需要进行修改,从而提高了系统的可维护性。但是,这也可能会导致系统的复杂度增加,因为外观类需要了解子系统的具体实现细节,以便为客户端提供一个统一的高层接口。
- 可能会降低系统的性能:外观模式为子系统中的一组接口提供了一个统一的高层接口,使得客户端的代码更加简洁。但是,这也可能会导致系统的性能降低,因为外观类需要将客户端的请求转发给子系统中的具体实现类,从而增加了系统的开销。
六、适用场景
- 当需要为一个复杂的子系统提供一个简单的接口时:外观模式可以为一个复杂的子系统提供一个简单的接口,使得客户端的代码更加简洁。
- 当需要提高系统的可维护性时:外观模式可以将子系统中的具体实现类隐藏起来,使得客户端只需要与外观类进行交互,而不需要了解子系统的具体实现细节。这样,当子系统中的具体实现类发生变化时,客户端的代码不需要进行修改,从而提高了系统的可维护性。
- 当需要提高系统的灵活性时:外观模式可以根据客户端的需求,动态地选择不同的子系统实现类,从而提高了系统的灵活性。
七、总结
外观模式是一种结构型设计模式,它为子系统中的一组接口提供一个统一的高层接口,使得子系统更容易使用。外观模式的优点是简化了客户端的代码、提高了系统的可维护性和灵活性;缺点是可能会增加系统的复杂度和降低系统的性能。外观模式适用于当需要为一个复杂的子系统提供一个简单的接口、提高系统的可维护性和灵活性的场景。
享元模式
一、定义
享元模式是一种结构型设计模式,它摒弃了在每个对象中保存所有数据的方式,通过共享多个对象所共有的相同状态,让你能在有限的内存容量中载入更多对象。
二、结构
- 享元(Flyweight):包含了一个对象的部分状态,这些状态可以在多个对象之间共享。
- 具体享元(Concrete Flyweight):实现了享元接口,包含了具体的共享状态。
- 享元工厂(Flyweight Factory):负责创建和管理享元对象。
- 客户端(Client):使用享元对象。
三、示例代码(用 C 语言汽车举例)
#include <stdio.h>
#include <stdlib.h>
// 定义汽车颜色枚举
typedef enum {
RED,
BLUE,
GREEN,
WHITE,
BLACK
} CarColor;
// 定义汽车品牌枚举
typedef enum {
BMW,
AUDI,
BENZ,
TOYOTA,
HONDA
} CarBrand;
// 定义汽车结构体
typedef struct {
CarColor color;
CarBrand brand;
} Car;
// 享元工厂结构体
typedef struct {
Car* cars[100];
int numCars;
} CarFactory;
// 创建享元工厂
CarFactory* createCarFactory() {
CarFactory* factory = (CarFactory*)malloc(sizeof(CarFactory));
factory->numCars = 0;
return factory;
}
// 获取汽车享元
Car* getCar(CarFactory* factory, CarColor color, CarBrand brand) {
for (int i = 0; i < factory->numCars; i++) {
if (factory->cars[i]->color == color && factory->cars[i]->brand == brand) {
return factory->cars[i];
}
}
Car* car = (Car*)malloc(sizeof(Car));
car->color = color;
car->brand = brand;
factory->cars[factory->numCars++] = car;
return car;
}
// 打印汽车信息
void printCar(Car* car) {
printf("Car: color = %d, brand = %d\\n", car->color, car->brand);
}
int main() {
// 创建享元工厂
CarFactory* factory = createCarFactory();
// 获取汽车享元
Car* car1 = getCar(factory, RED, BMW);
Car* car2 = getCar(factory, BLUE, AUDI);
Car* car3 = getCar(factory, GREEN, BENZ);
Car* car4 = getCar(factory, WHITE, TOYOTA);
Car* car5 = getCar(factory, BLACK, HONDA);
// 打印汽车信息
printCar(car1);
printCar(car2);
printCar(car3);
printCar(car4);
printCar(car5);
// 释放内存
free(car1);
free(car2);
free(car3);
free(car4);
free(car5);
free(factory);
return 0;
}
在上述示例中,我们定义了汽车颜色和品牌的枚举类型,以及汽车结构体。然后,我们实现了享元工厂,用于创建和管理汽车享元对象。在getCar
方法中,我们首先检查工厂中是否已经存在具有相同颜色和品牌的汽车享元对象,如果存在则直接返回该对象,否则创建一个新的汽车享元对象并添加到工厂中。最后,我们在main
函数中创建了一个享元工厂,并通过getCar
方法获取了多个汽车享元对象,并打印了它们的信息。
四、优点
- 减少内存占用:通过共享相同的状态,减少了内存的占用。
- 提高性能:避免了重复创建相同的对象,提高了程序的性能。
- 增强代码的可维护性:将对象的状态分离出来,使得代码更加清晰易读,易于维护。
五、缺点
- 增加了系统的复杂性:需要额外的代码来管理享元对象,增加了系统的复杂性。
- 可能会导致外部状态的问题:如果享元对象需要依赖外部状态,可能会导致一些问题,例如线程安全问题。
- 不适合用于频繁变化的对象:如果对象的状态经常变化,那么使用享元模式可能会导致性能下降。
六、适用场景
- 系统中存在大量相似的对象:如果系统中存在大量相似的对象,例如文本编辑器中的字符、图形编辑器中的图形等,那么使用享元模式可以减少内存的占用,提高程序的性能。
- 对象的状态可以共享:如果对象的状态可以共享,例如汽车的颜色、品牌等,那么使用享元模式可以减少内存的占用,提高程序的性能。
- 需要提高系统的性能:如果系统的性能是一个关键因素,那么使用享元模式可以提高程序的性能。
七、总结
享元模式是一种通过共享对象来减少内存占用和提高性能的设计模式。它适用于系统中存在大量相似对象且对象的状态可以共享的场景。通过使用享元模式,可以将对象的状态分离出来,使得代码更加清晰易读,易于维护。但是,享元模式也有一些缺点,例如增加了系统的复杂性、可能会导致外部状态的问题以及不适合用于频繁变化的对象等。因此,在使用享元模式时,需要根据具体情况进行权衡和选择。
代理模式
一、定义
代理模式是一种结构型设计模式,让你能够提供对象的替代品或其占位符。代理控制着对于原对象的访问,并允许在将请求提交给对象前后进行一些处理。
二、结构
- 服务接口(Service Interface):声明了服务接口。代理必须遵循该接口才能伪装成服务对象。
- 服务(Service):类提供了一些实用的业务逻辑。
- 代理(Proxy):包含一个指向服务对象的引用成员变量。代理完成其任务(例如延迟初始化、记录日志、访问控制和缓存等)后会将请求传递给服务对象。通常情况下,代理会对其服务对象的整个生命周期进行管理。
- 客户端(Client):能通过同一接口与服务或代理进行交互,所以你可在一切需要服务对象的代码中使用代理。
三、示例代码(用 C 语言汽车举例)
#include <stdio.h>
// 服务接口
typedef struct {
void (*drive)(void);
} Car;
// 服务实现
typedef struct {
Car car;
} RealCar;
void realCarDrive(void) {
printf("Real Car is driving...\\n");
}
// 代理
typedef struct {
RealCar *realCar;
} CarProxy;
void carProxyDrive(void) {
printf("Car Proxy is checking...\\n");
if (carProxy->realCar!= NULL) {
carProxy->realCar->drive();
} else {
printf("Real Car is not available.\\n");
}
}
// 客户端
int main() {
// 创建代理对象
CarProxy carProxy = {NULL};
// 模拟请求
carProxyDrive(&carProxy);
// 创建真实汽车对象
RealCar realCar = {(Car){realCarDrive}};
// 将真实汽车对象与代理关联
carProxy.realCar = &realCar;
// 再次模拟请求
carProxyDrive(&carProxy);
return 0;
}
在上述示例中,我们定义了一个Car
接口,其中包含一个drive
方法。RealCar
结构体实现了Car
接口,代表真实的汽车。CarProxy
结构体是Car
接口的代理,它包含一个指向RealCar
对象的指针。
在main
函数中,我们首先创建了一个CarProxy
对象。然后,我们模拟了一个请求,此时代理会检查真实汽车是否可用。由于我们还没有创建真实汽车对象,所以代理会输出“Real Car is not available.”。
接下来,我们创建了一个RealCar
对象,并将其与代理关联。最后,我们再次模拟请求,此时代理会将请求转发给真实汽车对象,输出“Real Car is driving...”。
四、优点
- 控制请求访问:代理可以控制对真实对象的访问,例如检查权限、缓存结果等。
- 延迟初始化:代理可以延迟创建真实对象,直到真正需要时才进行创建,从而提高性能。
- 保护真实对象:代理可以保护真实对象,避免其直接暴露在客户端代码中,从而提高安全性。
- 实现远程访问:代理可以实现远程访问,例如通过网络连接访问远程对象。
五、缺点
- 增加系统复杂度:代理模式需要引入额外的代理对象,增加了系统的复杂度。
- 可能会降低性能:代理模式可能会降低系统的性能,因为代理对象需要进行额外的处理。
- 可能会导致代码冗余:代理模式可能会导致代码冗余,因为代理对象需要实现与真实对象相同的接口。
六、适用场景
- 远程代理:适用于需要访问远程对象的场景,例如通过网络连接访问远程服务器上的对象。
- 虚拟代理:适用于需要延迟初始化的场景,例如创建一个大型对象时,可以先创建一个虚拟代理,等到真正需要时再创建真实对象。
- 保护代理:适用于需要保护真实对象的场景,例如限制对真实对象的访问权限。
- 智能引用:适用于需要在没有客户端使用某个重量级对象时立即销毁该对象的场景。
七、总结
代理模式是一种非常有用的设计模式,它可以控制对真实对象的访问,延迟初始化,保护真实对象,实现远程访问等。但是,代理模式也有一些缺点,例如增加系统复杂度,可能会降低性能,可能会导致代码冗余等。因此,在使用代理模式时,需要根据具体情况进行权衡,选择合适的代理模式。
六、行为型模式
责任链模式(Chain of Responsibility Pattern)
定义
责任链模式是一种行为设计模式,它允许多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递请求,直到有一个对象处理它为止。
结构
责任链模式主要包括以下几个部分:
- 抽象处理者(Handler):定义处理请求的接口,并包含对下一个处理者的引用。
- 具体处理者(ConcreteHandler):实现处理请求的具体处理者,处理它所负责的请求,如果不能处理则将请求传递给下一个处理者。
- 客户端(Client):向链上的具体处理者对象提交请求。
示例代码
假设我们要设计一个汽车应用程序,模拟不同的汽车零部件(如引擎、车轮、制动系统)检查的责任链。我们使用责任链模式来实现这一点。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 抽象处理者:汽车检查器
typedef struct CarInspector {
struct CarInspector* nextInspector;
void (*inspect)(struct CarInspector*, const char*);
} CarInspector;
// 设置下一个检查者
void setNextInspector(CarInspector* currentInspector, CarInspector* nextInspector) {
currentInspector->nextInspector = nextInspector;
}
// 引擎检查器
typedef struct EngineInspector {
CarInspector base;
} EngineInspector;
void inspectEngine(CarInspector* inspector, const char* component) {
if (strcmp(component, "Engine") == 0) {
printf("Inspecting the engine.\\n");
} else if (inspector->nextInspector != NULL) {
inspector->nextInspector->inspect(inspector->nextInspector, component);
} else {
printf("No inspector available for component: %s\\n", component);
}
}
EngineInspector* createEngineInspector() {
EngineInspector* engineInspector = (EngineInspector*)malloc(sizeof(EngineInspector));
engineInspector->base.nextInspector = NULL;
engineInspector->base.inspect = inspectEngine;
return engineInspector;
}
// 车轮检查器
typedef struct WheelInspector {
CarInspector base;
} WheelInspector;
void inspectWheel(CarInspector* inspector, const char* component) {
if (strcmp(component, "Wheel") == 0) {
printf("Inspecting the wheel.\\n");
} else if (inspector->nextInspector != NULL) {
inspector->nextInspector->inspect(inspector->nextInspector, component);
} else {
printf("No inspector available for component: %s\\n", component);
}
}
WheelInspector* createWheelInspector() {
WheelInspector* wheelInspector = (WheelInspector*)malloc(sizeof(WheelInspector));
wheelInspector->base.nextInspector = NULL;
wheelInspector->base.inspect = inspectWheel;
return wheelInspector;
}
// 制动系统检查器
typedef struct BrakeInspector {
CarInspector base;
} BrakeInspector;
void inspectBrake(CarInspector* inspector, const char* component) {
if (strcmp(component, "Brake") == 0) {
printf("Inspecting the brake system.\\n");
} else if (inspector->nextInspector != NULL) {
inspector->nextInspector->inspect(inspector->nextInspector, component);
} else {
printf("No inspector available for component: %s\\n", component);
}
}
BrakeInspector* createBrakeInspector() {
BrakeInspector* brakeInspector = (BrakeInspector*)malloc(sizeof(BrakeInspector));
brakeInspector->base.nextInspector = NULL;
brakeInspector->base.inspect = inspectBrake;
return brakeInspector;
}
// 客户端代码
int main() {
// 创建具体的检查者对象
EngineInspector* engineInspector = createEngineInspector();
WheelInspector* wheelInspector = createWheelInspector();
BrakeInspector* brakeInspector = createBrakeInspector();
// 设置责任链
setNextInspector((CarInspector*)engineInspector, (CarInspector*)wheelInspector);
setNextInspector((CarInspector*)wheelInspector, (CarInspector*)brakeInspector);
// 测试责任链
printf("Testing engine inspection:\\n");
engineInspector->base.inspect((CarInspector*)engineInspector, "Engine");
printf("\\nTesting wheel inspection:\\n");
engineInspector->base.inspect((CarInspector*)engineInspector, "Wheel");
printf("\\nTesting brake inspection:\\n");
engineInspector->base.inspect((CarInspector*)engineInspector, "Brake");
printf("\\nTesting unknown component inspection:\\n");
engineInspector->base.inspect((CarInspector*)engineInspector, "Door");
// 清理内存
free(engineInspector);
free(wheelInspector);
free(brakeInspector);
return 0;
}
主要结构说明
- 抽象处理者(CarInspector):定义了处理请求的接口
inspect
,并包含对下一个处理者的引用nextInspector
。 - 具体处理者(EngineInspector、WheelInspector、BrakeInspector):实现了处理请求的方法
inspect
,并在处理不了请求时将其传递给下一个处理者。 - 客户端(main函数):创建具体处理者对象,设置责任链,并提交请求进行测试。
优点
- 降低耦合度:将请求的发送者和接收者解耦,使得多个处理者有机会处理该请求。
- 提高灵活性:可以动态地添加或删除责任链中的处理者,增加了系统的灵活性。
- 增强可扩展性:新的处理者可以很方便地加入到责任链中。
缺点
- 调试困难:由于请求是在责任链中传递的,可能会导致调试和跟踪较为困难。
- 性能问题:如果责任链过长,可能会导致性能问题,因为请求要经过多个处理者。
适用场景
- 多个对象可以处理同一请求:但是具体由哪个对象处理请求在运行时动态决定。
- 需要动态指定处理请求的对象:并且可以灵活地改变处理者的顺序。
总结
责任链模式通过将请求沿着处理者链传递,使得多个处理者都有机会处理请求,降低了发送者和接收者的耦合度。在汽车应用中,责任链模式可以用于不同的汽车零部件检查,展示了责任链模式如何在实际项目中使用。
- 命令模式
命令模式(Command Pattern)
定义
命令模式是一种行为型设计模式,它将请求封装成对象,从而使你可以用不同的请求对客户进行参数化,并且支持请求的排队、记录日志以及撤销操作。
结构
命令模式主要包括以下几个部分:
- 命令接口(Command):定义执行命令的接口。
- 具体命令(ConcreteCommand):实现命令接口,绑定接收者对象,调用接收者的相应操作。
- 接收者(Receiver):执行具体操作的类。
- 调用者(Invoker):持有命令对象并通过命令对象来执行请求。
- 客户端(Client):创建具体命令对象并设置其接收者。
示例代码
假设我们要设计一个汽车应用程序,不同的命令可以控制汽车的操作(如启动发动机、停止发动机、加速等)。我们使用命令模式来实现这一点。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 定义命令接口
typedef struct Command {
void (*execute)(struct Command*);
void (*undo)(struct Command*);
} Command;
// 定义接收者:汽车
typedef struct Car {
char* model;
} Car;
void startEngine(Car* car) {
printf("Engine of %s is starting.\\n", car->model);
}
void stopEngine(Car* car) {
printf("Engine of %s is stopping.\\n", car->model);
}
void accelerate(Car* car) {
printf("Car %s is accelerating.\\n", car->model);
}
// 定义具体命令:启动引擎命令
typedef struct StartEngineCommand {
Command command;
Car* car;
} StartEngineCommand;
void executeStartEngine(Command* command) {
StartEngineCommand* startCommand = (StartEngineCommand*)command;
startEngine(startCommand->car);
}
void undoStartEngine(Command* command) {
StartEngineCommand* startCommand = (StartEngineCommand*)command;
stopEngine(startCommand->car);
}
StartEngineCommand* createStartEngineCommand(Car* car) {
StartEngineCommand* command = (StartEngineCommand*)malloc(sizeof(StartEngineCommand));
command->car = car;
command->command.execute = executeStartEngine;
command->command.undo = undoStartEngine;
return command;
}
// 定义具体命令:停止引擎命令
typedef struct StopEngineCommand {
Command command;
Car* car;
} StopEngineCommand;
void executeStopEngine(Command* command) {
StopEngineCommand* stopCommand = (StopEngineCommand*)command;
stopEngine(stopCommand->car);
}
void undoStopEngine(Command* command) {
StopEngineCommand* stopCommand = (StopEngineCommand*)command;
startEngine(stopCommand->car);
}
StopEngineCommand* createStopEngineCommand(Car* car) {
StopEngineCommand* command = (StopEngineCommand*)malloc(sizeof(StopEngineCommand));
command->car = car;
command->command.execute = executeStopEngine;
command->command.undo = undoStopEngine;
return command;
}
// 定义调用者
typedef struct Invoker {
Command* command;
} Invoker;
void setCommand(Invoker* invoker, Command* command) {
invoker->command = command;
}
void executeCommand(Invoker* invoker) {
if (invoker->command != NULL) {
invoker->command->execute(invoker->command);
}
}
void undoCommand(Invoker* invoker) {
if (invoker->command != NULL) {
invoker->command->undo(invoker->command);
}
}
// 测试命令模式
int main() {
// 创建接收者对象:汽车
Car car = { .model = "Toyota Camry" };
// 创建具体命令对象
StartEngineCommand* startCommand = createStartEngineCommand(&car);
StopEngineCommand* stopCommand = createStopEngineCommand(&car);
// 创建调用者对象
Invoker invoker = { .command = NULL };
// 客户端设置并执行命令
setCommand(&invoker, (Command*)startCommand);
executeCommand(&invoker);
// 撤销命令
undoCommand(&invoker);
// 设置并执行停止引擎命令
setCommand(&invoker, (Command*)stopCommand);
executeCommand(&invoker);
// 撤销停止引擎命令
undoCommand(&invoker);
// 清理内存
free(startCommand);
free(stopCommand);
return 0;
}
主要结构说明
- 命令接口(Command):定义了执行命令和撤销命令的接口。
- 具体命令(ConcreteCommand):实现了命令接口,将请求委托给接收者执行。例子中包括启动引擎命令和停止引擎命令。
- 接收者(Receiver):执行实际操作的对象,例子中为
Car
。 - 调用者(Invoker):持有命令对象并通过命令对象来执行请求。例子中
Invoker
负责调用命令的执行和撤销操作。 - 客户端(Client):创建具体命令对象并设置其接收者,例子中为
main
函数。
优点
- 解耦请求发送者和接收者:命令模式将请求封装成对象,使请求的发送者与接收者解耦。
- 支持撤销和重做:命令对象可以实现撤销和重做操作。
- 命令的组合:可以将多个命令组合成一个复合命令,从而支持宏命令。
- 扩展性强:可以很容易地增加新的命令。
缺点
- 类数量增加:每个具体命令都需要一个对应的类,可能会导致类数量大幅增加。
- 增加系统复杂性:引入了许多新类和对象,增加了系统的复杂性。
适用场景
- 需要对请求排队和记录日志:命令对象可以支持请求的排队、记录日志和事务操作。
- 需要支持撤销和重做操作:命令模式可以很方便地实现操作的撤销和重做功能。
- 请求的发送者和接收者需要解耦:通过命令对象来解耦请求的发送者和接收者。
总结
命令模式通过将请求封装成对象,实现了请求发送者与接收者的解耦,并支持请求的撤销和重做操作。它适用于需要对请求进行排队、记录日志以及支持撤销和重做操作的场景。在汽车应用中,命令模式可以用于控制汽车的各种操作,如启动引擎、停止引擎等。
- 迭代器模式
迭代器模式(Iterator Pattern)
定义
迭代器模式是一种行为型设计模式,它提供一种方法顺序访问一个聚合对象中的各个元素,而不暴露该对象的内部表示。迭代器模式将遍历聚合对象的责任从聚合对象本身转移到了迭代器对象。
结构
迭代器模式主要包括以下几个部分:
- 迭代器接口(Iterator):定义访问和遍历元素的接口。
- 具体迭代器(ConcreteIterator):实现迭代器接口,并负责遍历聚合对象中的元素。
- 聚合接口(Aggregate):定义创建迭代器对象的接口。
- 具体聚合(ConcreteAggregate):实现聚合接口,并提供一个方法来返回具体迭代器的实例。
示例代码
假设我们要设计一个汽车应用程序,管理一系列的汽车对象。我们使用迭代器模式来遍历这些汽车对象。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 汽车结构体
typedef struct {
char* model;
int year;
} Car;
// 迭代器接口
typedef struct Iterator {
void (*first)(struct Iterator*);
void (*next)(struct Iterator*);
int (*isDone)(struct Iterator*);
Car* (*currentItem)(struct Iterator*);
void* aggregate;
int currentIndex;
} Iterator;
// 汽车集合结构体
typedef struct {
Car** cars;
int size;
int capacity;
} CarCollection;
// 创建汽车集合
CarCollection* createCarCollection(int capacity) {
CarCollection* collection = (CarCollection*)malloc(sizeof(CarCollection));
collection->cars = (Car**)malloc(sizeof(Car*) * capacity);
collection->size = 0;
collection->capacity = capacity;
return collection;
}
// 向汽车集合中添加汽车
void addCar(CarCollection* collection, Car* car) {
if (collection->size < collection->capacity) {
collection->cars[collection->size++] = car;
}
}
// 迭代器方法实现
void first(Iterator* iterator) {
iterator->currentIndex = 0;
}
void next(Iterator* iterator) {
iterator->currentIndex++;
}
int isDone(Iterator* iterator) {
CarCollection* collection = (CarCollection*)iterator->aggregate;
return iterator->currentIndex >= collection->size;
}
Car* currentItem(Iterator* iterator) {
CarCollection* collection = (CarCollection*)iterator->aggregate;
if (iterator->currentIndex < collection->size) {
return collection->cars[iterator->currentIndex];
}
return NULL;
}
// 创建汽车集合的迭代器
Iterator* createIterator(CarCollection* collection) {
Iterator* iterator = (Iterator*)malloc(sizeof(Iterator));
iterator->first = first;
iterator->next = next;
iterator->isDone = isDone;
iterator->currentItem = currentItem;
iterator->aggregate = collection;
iterator->currentIndex = 0;
return iterator;
}
// 测试迭代器模式
int main() {
// 创建汽车集合
CarCollection* collection = createCarCollection(3);
// 创建汽车对象
Car* car1 = (Car*)malloc(sizeof(Car));
car1->model = "Toyota Camry";
car1->year = 2023;
Car* car2 = (Car*)malloc(sizeof(Car));
car2->model = "Honda Accord";
car2->year = 2022;
Car* car3 = (Car*)malloc(sizeof(Car));
car3->model = "Tesla Model 3";
car3->year = 2024;
// 将汽车对象添加到集合中
addCar(collection, car1);
addCar(collection, car2);
addCar(collection, car3);
// 创建迭代器
Iterator* iterator = createIterator(collection);
// 使用迭代器遍历汽车集合
for (iterator->first(iterator); !iterator->isDone(iterator); iterator->next(iterator)) {
Car* car = iterator->currentItem(iterator);
if (car != NULL) {
printf("Car Model: %s, Year: %d\\n", car->model, car->year);
}
}
// 清理内存
free(car1);
free(car2);
free(car3);
free(collection->cars);
free(collection);
free(iterator);
return 0;
}
主要结构说明
- 迭代器接口(Iterator):定义了遍历方法
first
、next
、isDone
和currentItem
。还包含一个指向聚合对象的指针aggregate
和当前索引currentIndex
。 - 具体迭代器(ConcreteIterator):实现了迭代器接口的方法,用于遍历
CarCollection
。 - 聚合接口(Aggregate):本例中隐含在
CarCollection
中,没有显式定义。 - 具体聚合(ConcreteAggregate):实现了
CarCollection
,包含汽车对象的集合。
优点
- 分离遍历算法:将遍历算法与集合对象分离,使得算法可以独立于集合对象变化。
- 统一接口:通过定义统一的遍历接口,使得不同的集合对象可以使用相同的遍历方法。
- 支持多种遍历方式:可以根据需要实现不同的迭代器,支持不同的遍历方式。
缺点
- 额外的开销:由于引入了迭代器对象,可能会增加额外的开销。
- 复杂性增加:增加了类的数量,可能会使系统变得复杂。
适用场景
- 需要遍历不同类型的集合对象:并且希望使用统一的遍历接口。
- 需要支持多种遍历方式:如前序遍历、后序遍历、中序遍历等。
- 希望分离遍历算法和集合对象:使得两者可以独立变化。
总结
迭代器模式通过定义统一的遍历接口,使得不同的集合对象可以使用相同的遍历方法。它适用于需要遍历不同类型的集合对象,并希望支持多种遍历方式的场景。在汽车应用中,迭代器模式可以用于遍历汽车集合对象,展示了迭代器模式如何在实际项目中使用。
中介者模式(Mediator Pattern)
定义
中介者模式是一种行为型设计模式,它通过引入一个中介对象来封装一系列对象之间的交互,使各个对象不需要显式地相互引用,从而实现松散耦合,并且可以独立地改变它们之间的交互。
结构
中介者模式主要包括以下几个部分:
- 中介者接口(Mediator):定义一个接口用于与各同事对象之间的通信。
- 具体中介者(ConcreteMediator):实现中介者接口,协调各同事对象之间的交互。
- 同事类(Colleague):各同事类只知道中介者,而不知道其他同事类。
示例代码
假设我们要设计一个汽车应用程序,其中包括不同的汽车部件(如引擎、变速箱)相互通信。我们使用中介者模式来实现这一点。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 定义中介者接口
typedef struct Mediator {
void (*notify)(struct Mediator*, const char* event);
} Mediator;
// 定义同事类接口
typedef struct Colleague {
Mediator* mediator;
} Colleague;
// 定义具体同事类:引擎
typedef struct Engine {
Colleague colleague;
void (*startEngine)(struct Engine*);
} Engine;
void startEngine(Engine* engine) {
printf("Engine started.\\n");
engine->colleague.mediator->notify(engine->colleague.mediator, "EngineStarted");
}
Engine* createEngine(Mediator* mediator) {
Engine* engine = (Engine*)malloc(sizeof(Engine));
engine->colleague.mediator = mediator;
engine->startEngine = startEngine;
return engine;
}
// 定义具体同事类:变速箱
typedef struct Transmission {
Colleague colleague;
void (*shiftGear)(struct Transmission*);
} Transmission;
void shiftGear(Transmission* transmission) {
printf("Transmission shifting gear.\\n");
transmission->colleague.mediator->notify(transmission->colleague.mediator, "GearShifted");
}
Transmission* createTransmission(Mediator* mediator) {
Transmission* transmission = (Transmission*)malloc(sizeof(Transmission));
transmission->colleague.mediator = mediator;
transmission->shiftGear = shiftGear;
return transmission;
}
// 定义具体中介者
typedef struct CarMediator {
Mediator mediator;
Engine* engine;
Transmission* transmission;
} CarMediator;
void notify(Mediator* mediator, const char* event) {
CarMediator* carMediator = (CarMediator*)mediator;
if (strcmp(event, "EngineStarted") == 0) {
printf("Mediator: Engine has started, now shifting gear.\\n");
carMediator->transmission->shiftGear(carMediator->transmission);
} else if (strcmp(event, "GearShifted") == 0) {
printf("Mediator: Gear has been shifted.\\n");
}
}
CarMediator* createCarMediator() {
CarMediator* mediator = (CarMediator*)malloc(sizeof(CarMediator));
mediator->mediator.notify = notify;
mediator->engine = NULL;
mediator->transmission = NULL;
return mediator;
}
// 测试中介者模式
int main() {
// 创建中介者
CarMediator* carMediator = createCarMediator();
// 创建同事类对象并设置中介者
Engine* engine = createEngine((Mediator*)carMediator);
Transmission* transmission = createTransmission((Mediator*)carMediator);
// 设置中介者的同事类
carMediator->engine = engine;
carMediator->transmission = transmission;
// 启动引擎
engine->startEngine(engine);
// 清理内存
free(engine);
free(transmission);
free(carMediator);
return 0;
}
主要结构说明
- 中介者接口(Mediator):定义了一个通知方法
notify
,用于同事类之间的通信。 - 具体中介者(ConcreteMediator):实现了中介者接口,协调同事类之间的交互。在本例中是
CarMediator
。 - 同事类(Colleague):包括引擎(
Engine
)和变速箱(Transmission
),它们通过中介者进行通信。
优点
- 降低耦合:中介者模式通过引入一个中介对象,使各同事类不需要显式地相互引用,降低了对象之间的耦合度。
- 简化对象交互:中介者封装了对象之间的交互逻辑,使得系统更容易理解和维护。
- 集中控制:中介者模式将交互逻辑集中到中介者中,使得修改和扩展交互行为更加方便。
缺点
- 中介者复杂性:随着同事类的增加,中介者的复杂性也会增加,可能会变得难以维护。
- 性能问题:中介者模式引入了额外的中介对象,可能会对系统性能产生一定的影响。
适用场景
- 对象之间存在复杂的引用关系:需要通过一个中介对象来管理这些关系。
- 需要解耦多个对象之间的交互:避免对象之间的直接依赖。
- 希望集中控制交互逻辑:使得系统更易于理解和维护。
总结
中介者模式通过引入一个中介对象,使各同事类不需要显式地相互引用,降低了对象之间的耦合度,并简化了对象之间的交互。在汽车应用中,中介者模式可以用于协调汽车部件(如引擎、变速箱)之间的交互,展示了中介者模式如何在实际项目中使用。
备忘录模式(Memento Pattern)
定义
备忘录模式是一种行为型设计模式,它允许在不破坏封装的前提下,捕获和恢复对象的内部状态。备忘录模式将对象的状态保存到一个备忘录对象中,以便以后恢复到之前的状态。
结构
备忘录模式主要包括以下几个部分:
- 发起人(Originator):创建一个包含其当前内部状态的备忘录对象,并使用备忘录对象恢复其内部状态。
- 备忘录(Memento):存储发起人的内部状态,并防止其他对象访问备忘录。备忘录一般是不可变的。
- 负责人(Caretaker):负责保存和恢复备忘录,但不能操作或检查备忘录的内容。
示例代码
假设我们要设计一个汽车应用程序,汽车的状态(如速度、燃油量)可以保存和恢复。我们使用备忘录模式来实现这一点。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 定义汽车状态结构体
typedef struct {
int speed;
int fuel;
} CarState;
// 定义备忘录结构体
typedef struct {
CarState state;
} Memento;
// 定义汽车结构体
typedef struct {
int speed;
int fuel;
} Car;
// 创建备忘录
Memento* createMemento(Car* car) {
Memento* memento = (Memento*)malloc(sizeof(Memento));
memento->state.speed = car->speed;
memento->state.fuel = car->fuel;
return memento;
}
// 从备忘录恢复状态
void restoreFromMemento(Car* car, Memento* memento) {
car->speed = memento->state.speed;
car->fuel = memento->state.fuel;
}
// 打印汽车状态
void printCarState(Car* car) {
printf("Car state - Speed: %d, Fuel: %d\\n", car->speed, car->fuel);
}
// 测试备忘录模式
int main() {
// 创建汽车对象
Car* car = (Car*)malloc(sizeof(Car));
car->speed = 100;
car->fuel = 50;
// 打印初始状态
printCarState(car);
// 创建备忘录并保存当前状态
Memento* memento = createMemento(car);
// 改变汽车状态
car->speed = 150;
car->fuel = 20;
printCarState(car);
// 从备忘录恢复状态
restoreFromMemento(car, memento);
printCarState(car);
// 清理内存
free(memento);
free(car);
return 0;
}
主要结构说明
- 发起人(Originator):
Car
结构体,包括汽车的状态(速度和燃油量),并包含创建备忘录和从备忘录恢复状态的方法。 - 备忘录(Memento):
Memento
结构体,存储汽车的状态。 - 负责人(Caretaker):本例中由
main
函数充当,负责保存和恢复备忘录。
优点
- 封装性:备忘录模式通过封装对象的状态,保持了对象的封装性,不会暴露对象的内部细节。
- 简化状态管理:通过备忘录,可以方便地保存和恢复对象的状态,简化了状态管理。
- 支持撤销操作:备忘录模式可以用于实现撤销操作,方便恢复到之前的状态。
缺点
- 内存消耗:如果对象的状态非常复杂,保存多个备忘录会占用较多内存。
- 性能开销:创建和恢复备忘录需要额外的计算开销,可能会影响性能。
- 实现复杂性:在某些情况下,实现备忘录模式可能会比较复杂,特别是涉及到对象的深拷贝。
适用场景
- 需要保存和恢复对象的状态:适用于需要频繁保存和恢复对象状态的场景,如撤销操作、历史记录等。
- 需要避免暴露对象的内部状态:通过备忘录模式,可以保持对象的封装性,避免暴露内部细节。
- 需要支持撤销操作:备忘录模式可以方便地实现撤销操作,恢复到之前的状态。
总结
备忘录模式通过封装对象的状态,提供了一种保存和恢复对象状态的方法。它保持了对象的封装性,简化了状态管理,并且可以用于实现撤销操作。在汽车应用中,备忘录模式可以用于保存和恢复汽车的状态,如速度和燃油量,展示了备忘录模式如何在实际项目中使用。
观察者模式(Observer Pattern)
定义
观察者模式是一种行为型设计模式,它定义了一种一对多的依赖关系,使得当一个对象的状态发生变化时,其相关依赖对象会得到通知并自动更新。这种模式有时也被称为发布-订阅模式。
结构
观察者模式主要包括以下几个部分:
- 主题(Subject):维护一个观察者列表,并提供注册、移除和通知观察者的方法。
- 观察者(Observer):定义一个更新接口,当主题的状态发生变化时通知观察者。
- 具体主题(ConcreteSubject):实现主题接口,维护主题的状态,并在状态改变时通知所有观察者。
- 具体观察者(ConcreteObserver):实现观察者接口,定义在接收到通知时的行为。
示例代码
假设我们要设计一个汽车应用程序,当汽车的状态(如速度、燃油量)发生变化时,通知相关的监控系统(如仪表盘、导航系统)。我们使用观察者模式来实现这一点。
#include <stdio.h>
#include <stdlib.h>
#define MAX_OBSERVERS 10
// 定义观察者接口
typedef struct Observer {
void (*update)(struct Observer*, int, int);
} Observer;
// 定义主题接口
typedef struct Subject {
void (*registerObserver)(struct Subject*, Observer*);
void (*removeObserver)(struct Subject*, Observer*);
void (*notifyObservers)(struct Subject*);
Observer* observers[MAX_OBSERVERS];
int observerCount;
int speed;
int fuel;
} Subject;
// 具体观察者:仪表盘
typedef struct {
Observer observer;
void (*update)(struct Observer*, int, int);
} Dashboard;
void updateDashboard(Observer* observer, int speed, int fuel) {
printf("Dashboard: Speed = %d, Fuel = %d\\n", speed, fuel);
}
Dashboard* createDashboard() {
Dashboard* dashboard = (Dashboard*)malloc(sizeof(Dashboard));
dashboard->observer.update = updateDashboard;
return dashboard;
}
// 具体观察者:导航系统
typedef struct {
Observer observer;
void (*update)(struct Observer*, int, int);
} Navigation;
void updateNavigation(Observer* observer, int speed, int fuel) {
printf("Navigation: Speed = %d, Fuel = %d\\n", speed, fuel);
}
Navigation* createNavigation() {
Navigation* navigation = (Navigation*)malloc(sizeof(Navigation));
navigation->observer.update = updateNavigation;
return navigation;
}
// 具体主题:汽车
typedef struct {
Subject subject;
} Car;
void registerObserver(Subject* subject, Observer* observer) {
if (subject->observerCount < MAX_OBSERVERS) {
subject->observers[subject->observerCount++] = observer;
}
}
void removeObserver(Subject* subject, Observer* observer) {
for (int i = 0; i < subject->observerCount; i++) {
if (subject->observers[i] == observer) {
for (int j = i; j < subject->observerCount - 1; j++) {
subject->observers[j] = subject->observers[j + 1];
}
subject->observerCount--;
break;
}
}
}
void notifyObservers(Subject* subject) {
for (int i = 0; i < subject->observerCount; i++) {
subject->observers[i]->update(subject->observers[i], subject->speed, subject->fuel);
}
}
Car* createCar() {
Car* car = (Car*)malloc(sizeof(Car));
car->subject.registerObserver = registerObserver;
car->subject.removeObserver = removeObserver;
car->subject.notifyObservers = notifyObservers;
car->subject.observerCount = 0;
car->subject.speed = 0;
car->subject.fuel = 0;
return car;
}
void setCarState(Car* car, int speed, int fuel) {
car->subject.speed = speed;
car->subject.fuel = fuel;
car->subject.notifyObservers((Subject*)car);
}
// 测试观察者模式
int main() {
// 创建汽车对象
Car* car = createCar();
// 创建观察者对象
Dashboard* dashboard = createDashboard();
Navigation* navigation = createNavigation();
// 注册观察者
car->subject.registerObserver((Subject*)car, (Observer*)dashboard);
car->subject.registerObserver((Subject*)car, (Observer*)navigation);
// 改变汽车状态,通知观察者
setCarState(car, 100, 50);
setCarState(car, 150, 20);
// 移除一个观察者
car->subject.removeObserver((Subject*)car, (Observer*)dashboard);
// 再次改变汽车状态,通知剩余观察者
setCarState(car, 200, 10);
// 清理内存
free(dashboard);
free(navigation);
free(car);
return 0;
}
主要结构说明
- 主题接口(Subject):定义了注册、移除和通知观察者的方法。
- 观察者接口(Observer):定义了更新方法
update
,当主题的状态发生变化时调用。 - 具体主题(ConcreteSubject):
Car
结构体,实现了主题接口,并维护主题的状态(速度和燃油量)。 - 具体观察者(ConcreteObserver):
Dashboard
和Navigation
结构体,实现了观察者接口,定义了在接收到通知时的行为。
优点
- 解耦:观察者模式使得主题和观察者之间的依赖关系减少,从而实现低耦合。
- 易于扩展:可以方便地增加或删除观察者,符合开放-关闭原则。
- 广播通信:主题可以向所有注册的观察者广播更新通知,简化了消息的传递。
缺点
- 通知延迟:如果观察者较多,或者通知的频率较高,可能会导致通知延迟。
- 复杂性增加:如果观察者和主题之间的依赖关系过于复杂,可能会增加系统的复杂性和维护成本。
- 无序更新:观察者接收通知的顺序可能是不确定的,如果顺序很重要,需要额外处理。
适用场景
- 状态变化通知:需要在一个对象状态发生变化时通知其他对象。
- 多级联动更新:一个对象的更新需要触发其他对象的更新,如图形界面中的数据绑定。
- 事件处理系统:用于实现事件处理和广播机制,如订阅-发布系统。
总结
观察者模式通过定义一对多的依赖关系,使得主题和观察者之间的交互更加灵活和松散耦合。在汽车应用中,观察者模式可以用于监控汽车状态的变化,并通知相关的监控系统,如仪表盘和导航系统,展示了观察者模式如何在实际项目中使用。
状态模式(State Pattern)
定义
状态模式是一种行为型设计模式,它允许对象在其内部状态改变时改变其行为。状态模式将状态的相关行为封装到独立的状态类中,使得状态切换更加明确和可管理。
结构
状态模式主要包括以下几个部分:
- 状态接口(State):定义一个接口,封装与上下文的一个特定状态相关的行为。
- 具体状态(ConcreteState):实现状态接口的具体状态类,每一个类实现一个与上下文的一个状态相关的行为。
- 上下文(Context):维护一个当前状态,并且可以切换状态。
示例代码
假设我们要设计一个汽车应用程序,汽车有不同的状态(如启动、行驶、停止)。我们使用状态模式来实现这一点。
#include <stdio.h>
#include <stdlib.h>
// 定义状态接口
typedef struct Car Car; // 前向声明
typedef struct State {
void (*start)(Car*);
void (*drive)(Car*);
void (*stop)(Car*);
} State;
// 定义汽车结构体
struct Car {
State* currentState;
};
// 启动状态
typedef struct {
State state;
} StartState;
void startStateStart(Car* car);
void startStateDrive(Car* car);
void startStateStop(Car* car);
StartState* createStartState() {
StartState* state = (StartState*)malloc(sizeof(StartState));
state->state.start = startStateStart;
state->state.drive = startStateDrive;
state->state.stop = startStateStop;
return state;
}
void startStateStart(Car* car) {
printf("Car is already started.\\n");
}
void startStateDrive(Car* car) {
printf("Car is now driving.\\n");
// 切换到行驶状态
extern State* drivingState;
car->currentState = drivingState;
}
void startStateStop(Car* car) {
printf("Car is stopping from start state.\\n");
// 切换到停止状态
extern State* stoppedState;
car->currentState = stoppedState;
}
// 行驶状态
typedef struct {
State state;
} DrivingState;
void drivingStateStart(Car* car);
void drivingStateDrive(Car* car);
void drivingStateStop(Car* car);
DrivingState* createDrivingState() {
DrivingState* state = (DrivingState*)malloc(sizeof(DrivingState));
state->state.start = drivingStateStart;
state->state.drive = drivingStateDrive;
state->state.stop = drivingStateStop;
return state;
}
void drivingStateStart(Car* car) {
printf("Car is already driving.\\n");
}
void drivingStateDrive(Car* car) {
printf("Car is already driving.\\n");
}
void drivingStateStop(Car* car) {
printf("Car is stopping from driving state.\\n");
// 切换到停止状态
extern State* stoppedState;
car->currentState = stoppedState;
}
// 停止状态
typedef struct {
State state;
} StoppedState;
void stoppedStateStart(Car* car);
void stoppedStateDrive(Car* car);
void stoppedStateStop(Car* car);
StoppedState* createStoppedState() {
StoppedState* state = (StoppedState*)malloc(sizeof(StoppedState));
state->state.start = stoppedStateStart;
state->state.drive = stoppedStateDrive;
state->state.stop = stoppedStateStop;
return state;
}
void stoppedStateStart(Car* car) {
printf("Car is starting from stop state.\\n");
// 切换到启动状态
extern State* startState;
car->currentState = startState;
}
void stoppedStateDrive(Car* car) {
printf("Car needs to be started first before driving.\\n");
}
void stoppedStateStop(Car* car) {
printf("Car is already stopped.\\n");
}
// 全局状态实例
State* startState;
State* drivingState;
State* stoppedState;
// 初始化全局状态
void initializeStates() {
startState = (State*)createStartState();
drivingState = (State*)createDrivingState();
stoppedState = (State*)createStoppedState();
}
// 释放全局状态
void releaseStates() {
free(startState);
free(drivingState);
free(stoppedState);
}
// 创建汽车
Car* createCar() {
Car* car = (Car*)malloc(sizeof(Car));
car->currentState = stoppedState; // 初始状态为停止
return car;
}
// 测试状态模式
int main() {
initializeStates();
Car* car = createCar();
car->currentState->start(car);
car->currentState->drive(car);
car->currentState->stop(car);
car->currentState->drive(car);
car->currentState->stop(car);
free(car);
releaseStates();
return 0;
}
主要结构说明
- 状态接口(State):定义了汽车的状态接口,包括
start
、drive
和stop
方法。 - 具体状态(ConcreteState):包括
StartState
、DrivingState
和StoppedState
,分别实现了状态接口的具体行为。 - 上下文(Context):
Car
结构体,维护当前状态并委托状态处理具体行为。
优点
- 状态切换清晰:状态模式将状态相关的行为封装在独立的状态类中,使状态切换更加清晰。
- 易于扩展:可以方便地添加新的状态或修改现有状态,而无需改变上下文的代码。
- 符合单一职责原则:每个状态类只负责与其状态相关的行为。
缺点
- 类的数量增加:每个具体状态都需要定义一个类,可能会导致类的数量增加。
- 状态切换的管理:需要手动管理状态的切换逻辑,可能会增加一定的复杂性。
适用场景
- 对象的行为依赖于其状态:对象的行为随状态变化而变化。
- 需要明确状态转换的场景:需要明确地定义对象在不同状态下的行为。
- 状态切换频繁:对象在运行时需要频繁切换状态。
总结
状态模式通过将状态相关的行为封装到独立的状态类中,使得状态切换更加清晰和可管理。在汽车应用中,状态模式可以用于表示汽车的不同状态(启动、行驶、停止),并根据状态的变化执行相应的行为,展示了状态模式如何在实际项目中使用。
策略模式(Strategy Pattern)
定义
策略模式是一种行为型设计模式,它定义了一系列算法,并将每一个算法封装起来,使它们可以相互替换,算法的变化不会影响使用算法的客户。策略模式使得算法可以独立于使用它的客户而变化。
结构
策略模式主要包括以下几个部分:
- 策略接口(Strategy):定义所有支持的算法的公共接口。
- 具体策略(ConcreteStrategy):实现策略接口的具体算法。
- 上下文(Context):维护一个策略对象的引用,并且可以动态地更换策略。
示例代码
假设我们要设计一个汽车应用程序,汽车有不同的驾驶模式(如经济模式、运动模式、普通模式)。我们使用策略模式来实现这一点。
#include <stdio.h>
#include <stdlib.h>
// 定义策略接口
typedef struct Car Car;
typedef struct {
void (*drive)(Car*);
} DriveStrategy;
// 定义汽车结构体
struct Car {
DriveStrategy* strategy;
};
// 具体策略:经济模式
typedef struct {
DriveStrategy strategy;
} EconomicMode;
void driveEconomic(Car* car) {
printf("Driving in economic mode: Saving fuel.\\n");
}
EconomicMode* createEconomicMode() {
EconomicMode* mode = (EconomicMode*)malloc(sizeof(EconomicMode));
mode->strategy.drive = driveEconomic;
return mode;
}
// 具体策略:运动模式
typedef struct {
DriveStrategy strategy;
} SportMode;
void driveSport(Car* car) {
printf("Driving in sport mode: Enhanced performance.\\n");
}
SportMode* createSportMode() {
SportMode* mode = (SportMode*)malloc(sizeof(SportMode));
mode->strategy.drive = driveSport;
return mode;
}
// 具体策略:普通模式
typedef struct {
DriveStrategy strategy;
} NormalMode;
void driveNormal(Car* car) {
printf("Driving in normal mode: Balanced performance.\\n");
}
NormalMode* createNormalMode() {
NormalMode* mode = (NormalMode*)malloc(sizeof(NormalMode));
mode->strategy.drive = driveNormal;
return mode;
}
// 设置策略
void setStrategy(Car* car, DriveStrategy* strategy) {
car->strategy = strategy;
}
// 创建汽车
Car* createCar() {
Car* car = (Car*)malloc(sizeof(Car));
car->strategy = NULL; // 初始没有策略
return car;
}
// 测试策略模式
int main() {
Car* car = createCar();
EconomicMode* economicMode = createEconomicMode();
SportMode* sportMode = createSportMode();
NormalMode* normalMode = createNormalMode();
setStrategy(car, (DriveStrategy*)economicMode);
car->strategy->drive(car);
setStrategy(car, (DriveStrategy*)sportMode);
car->strategy->drive(car);
setStrategy(car, (DriveStrategy*)normalMode);
car->strategy->drive(car);
// 清理内存
free(economicMode);
free(sportMode);
free(normalMode);
free(car);
return 0;
}
主要结构说明
- 策略接口(Strategy):定义了
drive
方法,表示不同的驾驶模式。 - 具体策略(ConcreteStrategy):包括
EconomicMode
、SportMode
和NormalMode
,分别实现了不同的驾驶模式。 - 上下文(Context):
Car
结构体,维护一个策略对象的引用,并提供设置策略的方法。
优点
- 算法的封装:策略模式将算法的实现和使用分离,使得算法可以独立于使用它的客户而变化。
- 易于扩展:可以方便地增加新的策略而无需修改上下文类,符合开放-关闭原则。
- 动态切换:可以在运行时动态切换策略,提供了更大的灵活性。
缺点
- 策略数目增加:如果策略过多,会导致类的数量增加,增加系统的复杂性。
- 上下文需要了解策略:上下文必须知道所有的策略,并根据需要选择合适的策略。
适用场景
- 需要动态选择算法:在运行时需要根据不同的条件选择不同的算法。
- 算法多变且易于扩展:算法经常变化并且需要增加新的算法。
- 避免多重条件语句:使用策略模式可以避免在上下文中使用大量的条件语句来选择算法。
总结
策略模式通过定义一系列算法,并将每一个算法封装起来,使得它们可以相互替换,算法的变化不会影响使用算法的客户。在汽车应用中,策略模式可以用于实现不同的驾驶模式(如经济模式、运动模式、普通模式),展示了策略模式如何在实际项目中使用。
模板方法模式(Template Method Pattern)
定义
模板方法模式是一种行为型设计模式,定义了一个算法的骨架,将一些步骤延迟到子类中实现。模板方法使得子类可以在不改变算法结构的情况下重新定义算法的某些步骤。
结构
模板方法模式通常由以下几个部分组成:
- 抽象类(Abstract Class):定义了算法的骨架,包括一些抽象方法来延迟到子类实现。
- 具体子类(Concrete Class):实现抽象类中的抽象方法,完成算法中特定步骤的具体实现。
示例代码
假设我们要设计一个汽车制造的程序,其中有两种具体的汽车制造流程:制造经济型汽车和制造豪华型汽车。这两种汽车制造过程中的某些步骤是相同的,而某些步骤则因车型不同而有所差异。我们可以使用模板方法模式来实现这一设计。
#include <stdio.h>
// 抽象类:汽车制造过程
typedef struct {
void (*assembleBody)();
void (*installEngine)();
void (*installWheels)();
void (*paint)();
void (*finalize)();
} CarManufacturingProcess;
// 具体类:制造经济型汽车
typedef struct {
CarManufacturingProcess super;
} EconomyCar;
void assembleBodyEconomy() {
printf("Assembling body of Economy Car.\\n");
}
void installEngineEconomy() {
printf("Installing engine of Economy Car.\\n");
}
void installWheelsEconomy() {
printf("Installing wheels of Economy Car.\\n");
}
void paintEconomy() {
printf("Painting Economy Car.\\n");
}
void finalizeEconomy() {
printf("Economy Car manufacturing finalized.\\n");
}
void constructEconomyCar() {
EconomyCar economyCar = {
.super = {
.assembleBody = assembleBodyEconomy,
.installEngine = installEngineEconomy,
.installWheels = installWheelsEconomy,
.paint = paintEconomy,
.finalize = finalizeEconomy
}
};
economyCar.super.assembleBody();
economyCar.super.installEngine();
economyCar.super.installWheels();
economyCar.super.paint();
economyCar.super.finalize();
}
// 具体类:制造豪华型汽车
typedef struct {
CarManufacturingProcess super;
} LuxuryCar;
void assembleBodyLuxury() {
printf("Assembling body of Luxury Car.\\n");
}
void installEngineLuxury() {
printf("Installing powerful engine of Luxury Car.\\n");
}
void installWheelsLuxury() {
printf("Installing large wheels of Luxury Car.\\n");
}
void paintLuxury() {
printf("Painting Luxury Car with metallic paint.\\n");
}
void finalizeLuxury() {
printf("Luxury Car manufacturing finalized with additional features.\\n");
}
void constructLuxuryCar() {
LuxuryCar luxuryCar = {
.super = {
.assembleBody = assembleBodyLuxury,
.installEngine = installEngineLuxury,
.installWheels = installWheelsLuxury,
.paint = paintLuxury,
.finalize = finalizeLuxury
}
};
luxuryCar.super.assembleBody();
luxuryCar.super.installEngine();
luxuryCar.super.installWheels();
luxuryCar.super.paint();
luxuryCar.super.finalize();
}
int main() {
printf("Manufacturing Economy Car:\\n");
constructEconomyCar();
printf("\\n");
printf("Manufacturing Luxury Car:\\n");
constructLuxuryCar();
return 0;
}
主要结构说明
- 抽象类(CarManufacturingProcess):定义了汽车制造过程的骨架,包括一系列步骤(方法),其中某些步骤是抽象的,延迟到具体子类实现。
- 具体类(EconomyCar 和 LuxuryCar):实现了抽象类中的抽象方法,完成了制造过程中的具体步骤。
优点
- 代码复用:模板方法将公共的步骤实现在抽象类中,避免了代码重复。
- 扩展性:子类可以通过覆盖抽象方法来改变算法的某些步骤,同时保持算法的整体结构不变。
- 控制流程:抽象类中的模板方法定义了算法的框架和步骤顺序,提高了代码的逻辑清晰性和可维护性。
缺点
- 复杂度增加:可能会导致类的数量增加,特别是有多个变种的算法时。
- 限制子类的灵活性:模板方法定义了算法的骨架,有时子类想要改变骨架可能比较困难。
适用场景
- 多个算法有共同的流程:有多个算法,且它们之间有一些共同的步骤。
- 避免代码重复:需要在多个类中实现相同的代码,但有些步骤因子类而异。
- 控制子类扩展:框架需要控制子类扩展,以确保它们不改变算法的结构。
总结
模板方法模式通过定义一个算法的骨架,将一些步骤延迟到子类中实现,从而使得不同的子类可以在不改变算法结构的情况下重新定义算法的某些步骤。在汽车制造的示例中,展示了如何通过模板方法模式实现经济型汽车和豪华型汽车的制造过程,突出了模板方法在实际项目中的应用。
访问者模式(Visitor Pattern)
定义
访问者模式是一种行为型设计模式,它允许你定义一些操作,而无需修改要操作的元素类。通过将操作封装在访问者对象中,可以在不改变各元素类的情况下增加新操作。访问者模式的核心思想是将数据结构与数据操作分离,使得操作可以独立变化而不影响数据结构。
结构
访问者模式的结构包括以下几个主要组成部分:
- 抽象访问者(Visitor):定义了对每个具体元素类(如 Car, SUV, Truck)中的各种操作,它包含一系列的
visit
方法,每个方法对应一个具体元素类。 - 具体访问者(ConcreteVisitor):实现了抽象访问者中声明的各种
visit
方法,完成对具体元素的操作。 - 元素(Element):定义了一个接收访问者对象的
accept
方法,通常是一个抽象类或接口,可以有多个具体子类。 - 具体元素(ConcreteElement):实现了
accept
方法,在其中调用访问者对象的对应方法以便对自身进行操作。 - 对象结构(Object Structure):一个元素的集合,提供
accept
方法以便访问者访问它的元素。
示例代码
以下是使用访问者模式实现的一个简单汽车应用示例:
#include <stdio.h>
// 前置声明
typedef struct Car Car;
typedef struct SUV SUV;
typedef struct Truck Truck;
typedef struct Visitor Visitor;
// 抽象访问者
struct Visitor {
void (*visitCar)(Car*);
void (*visitSUV)(SUV*);
void (*visitTruck)(Truck*);
};
// 抽象元素:汽车
struct Car {
char* model;
int year;
void (*accept)(Car*, Visitor*);
};
// 抽象元素:SUV
struct SUV {
char* model;
int year;
void (*accept)(SUV*, Visitor*);
};
// 抽象元素:卡车
struct Truck {
char* model;
int year;
void (*accept)(Truck*, Visitor*);
};
// 具体访问者:汽车特性展示访问者
typedef struct {
Visitor super;
} CarFeaturesVisitor;
void visitCar(Car* car) {
printf("Visiting Car: Model %s, Year %d\\n", car->model, car->year);
}
void visitSUV(SUV* suv) {
printf("Visiting SUV: Model %s, Year %d\\n", suv->model, suv->year);
}
void visitTruck(Truck* truck) {
printf("Visiting Truck: Model %s, Year %d\\n", truck->model, truck->year);
}
// 具体元素实现 accept 方法
void carAccept(Car* car, Visitor* visitor) {
car->accept = carAccept;
visitor->visitCar(car);
}
void suvAccept(SUV* suv, Visitor* visitor) {
suv->accept = suvAccept;
visitor->visitSUV(suv);
}
void truckAccept(Truck* truck, Visitor* visitor) {
truck->accept = truckAccept;
visitor->visitTruck(truck);
}
// 测试代码
int main() {
CarFeaturesVisitor carFeaturesVisitor = {
.super = {
.visitCar = visitCar,
.visitSUV = visitSUV,
.visitTruck = visitTruck
}
};
Car car = {
.model = "Toyota Camry",
.year = 2023,
.accept = carAccept
};
SUV suv = {
.model = "Honda CR-V",
.year = 2022,
.accept = suvAccept
};
Truck truck = {
.model = "Ford F-150",
.year = 2021,
.accept = truckAccept
};
car.accept(&car, (Visitor*)&carFeaturesVisitor);
suv.accept(&suv, (Visitor*)&carFeaturesVisitor);
truck.accept(&truck, (Visitor*)&carFeaturesVisitor);
return 0;
}
优点
- 分离关注点:访问者模式将数据结构与操作分离,使得可以在不改变元素类的情况下增加新操作。
- 增加新操作:通过增加新的具体访问者,可以方便地在现有系统中添加新的操作,符合开闭原则。
- 集中相关操作:将相关的操作集中到一个访问者中,使得代码更加易于维护和理解。
缺点
- 增加新元素困难:如果需要在系统中增加新的元素类型,需要修改所有具体访问者类,可能导致系统的复杂性增加。
- 破坏封装:访问者模式要求访问者对象访问元素的内部状态,这可能会破坏元素对象的封装性。
适用场景
- 当一个对象结构包含多个类型的对象,且希望对这些对象进行不同的操作时,可以考虑使用访问者模式。
- 当需要对现有的对象结构添加新的操作而不改变其结构时,访问者模式是一个比较好的选择。
- 当对象结构中的元素类经常变化,但操作算法相对稳定时,访问者模式可以帮助我们避免在每个元素类中增加新操作。
总结
访问者模式通过将数据结构与数据操作分离,提供了一种灵活的方式来处理多种类型的元素及其操作。它能够有效地增加新的操作而不改变元素类结构,但也可能会导致系统的复杂性增加,特别是在增加新元素类型时需要谨慎设计。在示例中,我们展示了如何通过访问者模式来访问不同类型的汽车并执行特定的操作,以便更好地理解和应用这种设计模式。
七、参考文献
- 《深入设计模式》
- 《设计模式:可复用面向对象软件的基础》
- 《重构与模式》
- 《代码整洁之道》
- 《敏捷软件开发:原则、模式与实践》
- 《Head First 设计模式》
- 《Effective Java》
- 《设计模式解析》
- 《大话设计模式》
- 《重构:改善既有代码的设计》