1.简介
原型模式(Prototype Pattern)是一种创建型设计模式,它允许一个对象通过复制现有的对象来创建新对象,而不是通过 new 关键字创建新对象。这种模式可以提高性能和复用性,特别是在创建新对象需要复杂的初始化或者资源密集型操作时。
2.通俗讲解
假设你是一位玩具制造商,你正在制作两种玩具模型:圆形小球和矩形积木。你想要快速地生产出大量的这些玩具模型,但是每次从头开始制作一个新的模型都需要花费很多时间和精力。于是,你决定采用一种更高效的方法——克隆现有的模型。
制作玩具模型的过程:
- 准备模具:
首先,你需要准备一个圆形小球的模型和一个矩形积木的模型。
模型包含了玩具的基本信息,比如颜色、大小等。 - 制作模型:
对于圆形小球,你需要记录它的半径。
对于矩形积木,你需要记录它的宽度和高度。 - 克隆模型:
一旦有了基本模型,你可以通过“克隆”来快速制造出更多的玩具。
“克隆”意味着复制一个模型的所有特征,比如颜色、大小等。 - 检查克隆的玩具:
你可以检查克隆出来的玩具是否与原始模型完全一样。
比如,圆形小球的克隆体应该有同样的颜色和半径;矩形积木的克隆体应该有同样的颜色、宽度和高度。 - 继续生产:
通过不断克隆这些模型,你可以快速地制造出大量的玩具。
3.实战
3.1.代码
在 C 语言中,没有直接支持对象的概念,但我们可以使用结构体和函数指针来模拟这一行为。下面是一个简单的例子,我们将实现一个 Shape 结构体,并且提供两种形状:圆形和矩形。
这段代码就像是一个小型的玩具工厂,其中包含了一个简单的生产线:
- 准备模型:
在代码中,圆形小球对应着 Circle 结构体,矩形积木对应着 Rectangle 结构体。
每个结构体都包含了模型的基本信息,比如圆形小球的半径,矩形积木的宽度和高度。 - 制作模型:
创建一个圆形小球的实例,记录它的半径。
创建一个矩形积木的实例,记录它的宽度和高度。 - 克隆模型:
使用 circle_clone 函数来克隆圆形小球。
使用 rectangle_clone 函数来克隆矩形积木。
克隆过程中会复制所有相关的数据,比如半径、宽度和高度。 - 检查克隆的模型:
通过打印克隆后的圆形小球和矩形积木的信息,我们可以验证它们是否与原始模型一致。 - 清理:
最后,我们需要释放所有分配的内存,以避免内存泄漏。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Shape 类型定义
typedef struct Shape {
char* name;
void (*clone)(struct Shape* source, struct Shape* target);
} Shape;
// Circle 类型定义
typedef struct Circle {
Shape shape;
int radius;
} Circle;
// Rectangle 类型定义
typedef struct Rectangle {
Shape shape;
int width;
int height;
} Rectangle;
// Shape 克隆函数
void shape_clone(Shape* source, Shape* target) {
target->name = strdup(source->name);
target->clone = source->clone;
}
// Circle 克隆函数
void circle_clone(Circle* source, Circle* target) {
shape_clone(&source->shape, &target->shape);
target->radius = source->radius;
}
// Rectangle 克隆函数
void rectangle_clone(Rectangle* source, Rectangle* target) {
shape_clone(&source->shape, &target->shape);
target->width = source->width;
target->height = source->height;
}
int main() {
// 创建 Circle 实例
Circle* circle = malloc(sizeof(Circle));
circle->shape.name = strdup("Circle");
circle->shape.clone = (void (*)(Shape*, Shape*))circle_clone;
circle->radius = 10;
// 创建 Rectangle 实例
Rectangle* rectangle = malloc(sizeof(Rectangle));
rectangle->shape.name = strdup("Rectangle");
rectangle->shape.clone = (void (*)(Shape*, Shape*))rectangle_clone;
rectangle->width = 20;
rectangle->height = 30;
// 克隆 Circle 和 Rectangle
Circle* cloned_circle = malloc(sizeof(Circle));
circle_clone(circle, cloned_circle);
Rectangle* cloned_rectangle = malloc(sizeof(Rectangle));
rectangle_clone(rectangle, cloned_rectangle);
// 打印信息
printf("Original Circle: %s with radius %d\n", circle->shape.name, circle->radius);
printf("Cloned Circle: %s with radius %d\n", cloned_circle->shape.name, cloned_circle->radius);
printf("Original Rectangle: %s with dimensions %dx%d\n", rectangle->shape.name, rectangle->width, rectangle->height);
printf("Cloned Rectangle: %s with dimensions %dx%d\n", cloned_rectangle->shape.name, cloned_rectangle->width, cloned_rectangle->height);
// 清理内存
free(circle->shape.name);
free(cloned_circle->shape.name);
free(rectangle->shape.name);
free(cloned_rectangle->shape.name);
free(circle);
free(cloned_circle);
free(rectangle);
free(cloned_rectangle);
return 0;
}
3.2.代码解析
- 原型模式的实现:
使用结构体 Shape 作为基类,并包含了一个名为 clone 的函数指针成员。
Circle 和 Rectangle 结构体分别继承了 Shape,并提供了它们各自的 clone 方法。 - 克隆功能:
在 main 函数中,我们创建了一个圆形和一个矩形,并为每个对象分配了内存。
我们使用 circle_clone 和 rectangle_clone 函数来创建圆形和矩形的副本。
副本具有与原对象相同的属性值。 - 内存管理:
在 shape_clone 函数中,我们使用 strdup 复制字符串,以确保 name 字段不会指向相同的内存位置。
在程序结束前,我们释放了所有分配的内存,避免内存泄漏。 - 性能考虑:
使用原型模式可以避免构造函数中的复杂初始化逻辑,从而提高创建新对象的速度。
如果对象的状态依赖于外部环境或需要复杂的计算,原型模式尤其有用。
总结来说,通过这个简单的示例,我们展示了如何在 C 语言中使用原型模式来简化对象的创建过程。这种方式不仅提高了代码的可读性和可维护性,还能够有效地减少对象初始化的时间消耗。
3.3.代码运行
Original Circle: Circle with radius 10
Cloned Circle: Circle with radius 10
Original Rectangle: Rectangle with dimensions 20x30
Cloned Rectangle: Rectangle with dimensions 20x30
3.4.结果分析
我们首先准备了两个模型:圆形小球和矩形积木。然后,我们使用这些模型来制造新的玩具。为了制造新玩具,我们只需简单地克隆现有的模型,这样就省去了重新制作模型的步骤。最后,我们检查新玩具是否与原来的模型相同。通过这种方式,我们可以快速地制造出大量一致的玩具模型,而无需每次都从头开始制作。这正是原型模式在代码中的应用。
4.总结
-
什么是原型模式?
在原型模式中,一个类声明了一个克隆方法,用于创建自己的副本。这些副本是原始对象的浅拷贝或深拷贝。浅拷贝只复制对象本身,而深拷贝会递归地复制对象及其内部的所有引用。 -
为什么使用原型模式?
减少初始化时间:如果创建一个对象需要复杂的初始化过程,那么克隆已有的实例会更快。
避免重复初始化:当对象的创建依赖于复杂的外部状态时,可以通过克隆已经设置好的对象来避免重复设置。
封装创建细节:对象的创建逻辑被封装在其内部,外部不需要知道具体的创建细节。