继承/多态/封装

多态

允许父类指针指向子类对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
//C++中的继承与多态
struct A
{
virtual void fun() //C++中的多态:通过虚函数实现
{
cout << "A:fun()" << endl;
}

int a;
};
struct B :public A //C++中的继承:B类公有继承A类
{
virtual void fun() //C++中的多态:通过虚函数实现(子类的关键字virtual可加可不加)
{
cout << "B:fun()" << endl;
}

int b;
};

//C语言模拟C++的继承与多态

typedef void(*FUN)(); //定义一个函数指针来实现对成员函数的继承

struct _A //父类
{
FUN _fun; //由于C语言中结构体不能包含函数,故只能用函数指针在外面实现

int _a;
};

struct _B //子类
{
_A _a_; //在子类中定义一个基类的对象即可实现对父类的继承
int _b;
};

void _fA() //父类的同名函数
{
printf("_A:_fun()\n");
}
void _fB() //子类的同名函数
{
printf("_B:_fun()\n");
}


void Test1()
{
//测试C++中的继承与多态
A a; //定义一个父类对象a
B b; //定义一个子类对象b

A* p1 = &a; //定义一个父类指针指向父类的对象
p1->fun(); //调用父类的同名函数
p1 = &b; //让父类指针指向子类的对象
p1->fun(); //调用子类的同名函数


//C语言模拟继承与多态的测试
_A _a; //定义一个父类对象_a
_B _b; //定义一个子类对象_b
_a._fun = _fA; //父类的对象调用父类的同名函数
_b._a_._fun = _fB; //子类的对象调用子类的同名函数

_A* p2 = &_a; //定义一个父类指针指向父类的对象
p2->_fun(); //调用父类的同名函数
p2 = (_A*)&_b; //让父类指针指向子类的对象,由于类型不匹配所以要进行强转
p2->_fun(); //调用子类的同名函数
}

对象-行为模式

参考文献原文

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/// c++
class music_file
{
HANDLE hFile;

public:
void music_file() {}
virtual ~music_file() {}
virtual void read_file() {}
virtual void play() {}
virtual void stop() {}
virtual void back() {}
virtual void front() {}
virtual void up() {}
virtual void down() {}
};

/// c
typedef struct _music_file
{
HANDLE hFile;
void (*read_file)(struct _music_file* pMusicFile);
void (*play)(struct _music_file* pMusicFile);
void (*stop)(struct _music_file* pMusicFile);
void (*back)(struct _music_file* pMusicFile);
void (*front)(struct _music_file* pMusicFile);
void (*down)(struct _music_file* pMusicFile);
void (*up)(struct _music_file* pMusicFile);
}music_file;

这个结构体 music_file 是一个典型的设计模式,称为“对象-行为模式”或“函数指针成员模式”。它通过在结构体中包含函数指针来实现类似面向对象编程中的方法。每个函数指针都对应于一个操作,可以对 music_file 对象执行特定的操作。

结构体成员解释

  • HANDLE hFile: 用于存储文件句柄,通常是一个文件的标识符。
  • void (*read_file)(struct _music_file* pMusicFile): 读取文件的函数指针。
  • void (*play)(struct _music_file* pMusicFile): 播放音乐文件的函数指针。
  • void (*stop)(struct _music_file* pMusicFile): 停止播放音乐文件的函数指针。
  • void (*back)(struct _music_file* pMusicFile): 后退(例如,跳到上一曲)的函数指针。
  • void (*front)(struct _music_file* pMusicFile): 前进(例如,跳到下一曲)的函数指针。
  • void (*down)(struct _music_file* pMusicFile): 降低音量的函数指针。
  • void (*up)(struct _music_file* pMusicFile): 提高音量的函数指针。

    使用示例

  1. 定义具体的函数实现
    首先,你需要定义每个操作的具体实现函数。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    void read_file_impl(struct _music_file* pMusicFile) {
    // 实现读取文件的逻辑
    printf("Reading file with handle: %p\n", pMusicFile->hFile);
    }
    void play_impl(struct _music_file* pMusicFile) {
    // 实现播放音乐的逻辑
    printf("Playing file with handle: %p\n", pMusicFile->hFile);
    }
    void stop_impl(struct _music_file* pMusicFile) {
    // 实现停止播放的逻辑
    printf("Stopping file with handle: %p\n", pMusicFile->hFile);
    }
    void back_impl(struct _music_file* pMusicFile) {
    // 实现后退的逻辑
    printf("Going back in file with handle: %p\n", pMusicFile->hFile);
    }
    void front_impl(struct _music_file* pMusicFile) {
    // 实现前进的逻辑
    printf("Going forward in file with handle: %p\n", pMusicFile->hFile);
    }
    void down_impl(struct _music_file* pMusicFile) {
    // 实现降低音量的逻辑
    printf("Lowering volume for file with handle: %p\n", pMusicFile->hFile);
    }
    void up_impl(struct _music_file* pMusicFile) {
    // 实现提高音量的逻辑
    printf("Raising volume for file with handle: %p\n", pMusicFile->hFile);
    }
  2. 创建和初始化 music_file 结构体
    接下来,你需要创建一个 music_file 结构体实例,并将其函数指针成员初始化为具体的实现函数。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    int main() {
    music_file myMusicFile;
    myMusicFile.hFile = (HANDLE)0x1234; // 假设这是文件的句柄
    myMusicFile.read_file = read_file_impl;
    myMusicFile.play = play_impl;
    myMusicFile.stop = stop_impl;
    myMusicFile.back = back_impl;
    myMusicFile.front = front_impl;
    myMusicFile.down = down_impl;
    myMusicFile.up = up_impl;
    // 使用结构体的方法
    myMusicFile.read_file(&myMusicFile);
    myMusicFile.play(&myMusicFile);
    myMusicFile.stop(&myMusicFile);
    myMusicFile.back(&myMusicFile);
    myMusicFile.front(&myMusicFile);
    myMusicFile.down(&myMusicFile);
    myMusicFile.up(&myMusicFile);
    return 0;
    }

    解释

  • 函数指针初始化:在 main 函数中,我们为 music_file 结构体的每个函数指针成员赋值,指向具体的实现函数。
  • 调用方法:通过 myMusicFile 结构体实例调用这些函数指针,就像调用对象的方法一样。
    这种设计模式使得 music_file 结构体的行为可以灵活地配置和扩展,类似于面向对象编程中的多态性。

单例模式(Singleton Pattern)

参考文献原文

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/// c++
#include <string.h>
#include <assert.h>

class object
{
public:
static class object* pObject;

static object* create_new_object()
{
if(NULL != pObject)
return pObject;

pObject = new object();
assert(NULL != pObject);
return pObject;
}

private:
object() {}
~object() {}
};

class object* object::pObject = NULL;


int main(int argc, char* argv[])
{
object* pGlobal = object::create_new_object();
return 1;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/// c
typedef struct _DATA
{
void* pData;
}DATA;

void* get_data()
{
static DATA* pData = NULL;

if(NULL != pData)
return pData;

pData = (DATA*)malloc(sizeof(DATA));
assert(NULL != pData);
return (void*)pData;
}

详细解释

你提供的代码实现了一个典型的“单例模式”(Singleton Pattern)。单例模式是一种设计模式,确保一个类只有一个实例,并提供一个全局访问点。在你的代码中,get_data 函数确保 DATA 结构体只有一个实例,并且这个实例可以通过 get_data 函数全局访问。

  1. 结构体定义
    1
    2
    3
    4
    typedef struct _DATA
    {
    void* pData;
    } DATA;
    • DATA 结构体包含一个 void* 类型的指针 pData,可以用来存储任何类型的数据。
  2. get_data 函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    void* get_data()
    {
    static DATA* pData = NULL;
    if (NULL != pData)
    return pData;

    pData = (DATA*)malloc(sizeof(DATA));
    assert(NULL != pData);
    return (void*)pData;
    }
    • 静态变量 pData

      • static DATA* pData = NULL;:静态变量 pData 在函数第一次被调用时初始化为 NULL,并且在函数的多次调用中保持其值。这意味着 pData 只会被初始化一次。
    • 检查 pData 是否已经初始化

      • if (NULL != pData):如果 pData 已经被初始化(即不为 NULL),则直接返回 pData
    • 初始化 pData

      • pData = (DATA*)malloc(sizeof(DATA));:如果 pDataNULL,则使用 malloc 分配内存,并将其地址赋值给 pData
      • assert(NULL != pData);:使用 assert 宏确保 malloc 分配内存成功。如果 malloc 失败(返回 NULL),程序会终止并显示错误信息。
    • 返回 pData

      • return (void*)pData;:将 pData 转换为 void* 类型并返回。这样可以确保 get_data 函数的返回类型与声明一致。

举个使用上述代码的例子

以下是一个示例程序,展示了两次调用 get_data 函数的情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef struct _DATA {
void* pData;
} DATA;
void* get_data() {
static DATA* pData = NULL;
if (NULL != pData)
return pData;

pData = (DATA*)malloc(sizeof(DATA));
assert(NULL != pData);
return (void*)pData;
}
void set_data(void* data, void* value) {
DATA* pData = (DATA*)data;
pData->pData = value;
}
void* get_data_value(void* data) {
DATA* pData = (DATA*)data;
return pData->pData;
}
int main() {
// 第一次调用 get_data
void* data1 = get_data();
printf("First call to get_data: %p\n", data1);

// 设置 pData 的值
int value = 42;
set_data(data1, &value);

// 获取并打印 pData 的值
int* retrieved_value1 = (int*)get_data_value(data1);
printf("The value stored in pData (first call) is: %d\n", *retrieved_value1);

// 第二次调用 get_data
void* data2 = get_data();
printf("Second call to get_data: %p\n", data2);

// 获取并打印 pData 的值
int* retrieved_value2 = (int*)get_data_value(data2);
printf("The value stored in pData (second call) is: %d\n", *retrieved_value2);

// 释放内存
free(data1);

return 0;
}

运行结果

当你编译并运行上述程序时,输出将会是:

1
2
3
4
First call to get_data: 0x7ffeeb20b9a0
The value stored in pData (first call) is: 42
Second call to get_data: 0x7ffeeb20b9a0
The value stored in pData (second call) is: 42

解释

  1. 第一次调用 get_data
    • data1 被赋值为 get_data 函数返回的 pData
    • 打印 data1 的地址。
    • 设置 pData 的值为 42
    • 获取并打印 pData 的值。
  2. 第二次调用 get_data
    • data2 被赋值为 get_data 函数返回的 pData
    • 打印 data2 的地址。
    • 获取并打印 pData 的值。

关键点

  • 地址相同data1data2 的地址是相同的,因为 get_data 函数在第二次调用时直接返回了已经创建的 pData
  • 值相同data1data2 指向同一个 DATA 结构体,因此它们的 pData 值是相同的。

释放内存

  • 注意:由于 data1data2 指向同一个 DATA 结构体,只需要释放一次内存。在示例中,我们在 main 函数的最后释放了 data1 的内存。
    1
    free(data1);
    释放 data1 的内存后,data2 也失去了其指向

单例模式的特点

  1. 唯一实例:确保在整个程序中,DATA 结构体只有一个实例。
  2. 全局访问点:提供一个全局访问点(get_data 函数),使得任何地方都可以获取到这个唯一实例。
  3. 延迟初始化:实例的创建是在第一次调用 get_data 函数时进行的,而不是在程序启动时立即创建,这样可以节省资源。

    使用场景

  • 资源管理:确保资源(如文件句柄、数据库连接等)在整个程序中只有一个实例,避免资源浪费和冲突。
  • 配置管理:全局配置对象,确保配置在程序的任何地方都能访问。
  • 日志记录:日志记录器对象,确保日志记录的统一性和一致性。

    注意事项

  • 线程安全:在多线程环境中,get_data 函数的实现需要考虑线程安全。可以使用互斥锁(mutex)来确保多线程环境下 pData 的初始化是线程安全的。
  • 内存管理:确保在程序结束时释放分配的内存,避免内存泄漏。

    示例:线程安全的单例模式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    #include <pthread.h>
    typedef struct _DATA
    {
    void* pData;
    } DATA;
    static DATA* pData = NULL;
    static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    void* get_data()
    {
    if (NULL != pData)
    return pData;

    pthread_mutex_lock(&mutex);
    if (NULL == pData)
    {
    pData = (DATA*)malloc(sizeof(DATA));
    assert(NULL != pData);
    }
    pthread_mutex_unlock(&mutex);

    return (void*)pData;
    }
    在这个线程安全的版本中,使用 pthread_mutex_lockpthread_mutex_unlock 来确保 pData 的初始化是线程安全的。

原型模式(Prototype Pattern)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/// c++
class data
{
public:
data () {}
virtual ~data() {}
virtual class data* copy() = 0;
};

class data_A : public data
{
public:
data_A() {}
~data_A() {}
class data* copy()
{
return new data_A();
}
};

class data_B : public data
{
public:
data_B() {}
~data_B() {}
class data* copy()
{
return new data_B();
}
};

class data* clone(class data* pData)
{
return pData->copy();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/// c
typedef struct _DATA
{
struct _DATA* (*copy) (struct _DATA* pData);
}DATA;

DATA data_A = {data_copy_A};

struct _DATA* data_copy_A(struct _DATA* pData)
{
DATA* pResult = (DATA*)malloc(sizeof(DATA));
assert(NULL != pResult);
memmove(pResult, pData, sizeof(DATA));
return pResult;
};

struct _DATA* clone(struct _DATA* pData)
{
return pData->copy(pData);
};

代码解释

你提供的代码实现了一个对象克隆的设计模式,通常被称为原型模式(Prototype Pattern)。原型模式是一种创建型设计模式,它允许你通过复制现有对象来创建新对象,而无需通过常规的构造函数来创建对象。

结构体定义

1
2
3
typedef struct _DATA {
struct _DATA* (*copy)(struct _DATA* pData);
} DATA;
  • struct _DATA* (*copy)(struct _DATA* pData);:这是一个函数指针,指向一个复制函数。这个函数接受一个 struct _DATA* 类型的参数,并返回一个 struct _DATA* 类型的指针。

    具体实例

    1
    DATA data_A = {data_copy_A};
  • data_A 是一个 DATA 类型的实例,其 copy 成员被初始化为 data_copy_A 函数。

    复制函数

    1
    2
    3
    4
    5
    6
    struct _DATA* data_copy_A(struct _DATA* pData) {
    DATA* pResult = (DATA*)malloc(sizeof(DATA));
    assert(NULL != pResult);
    memmove(pResult, pData, sizeof(DATA));
    return pResult;
    }
  • data_copy_A 函数负责创建一个新的 DATA 实例,并将 pData 的内容复制到新实例中。
  • malloc 用于分配内存。
  • assert 用于确保内存分配成功。
  • memmove 用于将 pData 的内容复制到新分配的内存中。
  • 返回新创建的 DATA 实例的指针。

    克隆函数

    1
    2
    3
    struct _DATA* clone(struct _DATA* pData) {
    return pData->copy(pData);
    }
  • clone 函数是一个通用的克隆函数,它调用 pData 对象的 copy 成员函数来创建一个新的对象。

    原型模式的特点

  1. 对象创建
    • 通过复制现有对象来创建新对象,而不是通过构造函数。
    • 这样可以避免复杂的构造过程,特别是在对象的创建过程非常复杂或昂贵时。
  2. 对象复制
    • 使用对象的 copy 方法来创建新的对象实例。
    • 这个方法通常是一个深复制(Deep Copy),确保新对象与原对象完全独立。
  3. 灵活性
    • 可以动态地添加或修改对象的复制行为,而无需修改客户端代码。
    • 客户端只需要调用 clone 方法,而不需要关心具体的复制实现。

      优点

  • 减少重复代码:通过复制现有对象,可以避免重复的初始化代码。
  • 提高性能:在某些情况下,复制现有对象比通过构造函数创建新对象更高效。
  • 灵活性:可以轻松地添加或修改对象的复制行为,而无需修改客户端代码。

    缺点

  • 深复制的复杂性:实现深复制可能比较复杂,特别是在对象包含复杂数据结构时。
  • 内存管理:需要确保正确地管理内存,避免内存泄漏。

    示例使用

    以下是一个示例程序,展示了如何使用 clone 函数来创建 DATA 对象的副本:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    #include <stdio.h>
    #include <stdlib.h>
    #include <assert.h>
    #include <string.h>

    typedef struct _DATA {
    struct _DATA* (*copy)(struct _DATA* pData);
    int value;
    } DATA;

    struct _DATA* data_copy_A(struct _DATA* pData) {
    DATA* pResult = (DATA*)malloc(sizeof(DATA));
    assert(NULL != pResult);
    memmove(pResult, pData, sizeof(DATA));
    return pResult;
    }

    struct _DATA* clone(struct _DATA* pData) {
    return pData->copy(pData);
    }

    int main() {
    // 创建一个 DATA 实例
    DATA data_A = {data_copy_A, 42};

    // 克隆 data_A
    DATA* data_B = clone(&data_A);

    // 打印原始和克隆对象的值
    printf("Original value: %d\n", data_A.value);
    printf("Cloned value: %d\n", data_B->value);

    // 修改克隆对象的值
    data_B->value = 84;
    printf("Modified cloned value: %d\n", data_B->value);

    // 确保原始对象的值没有改变
    printf("Original value after modification: %d\n", data_A.value);

    // 释放内存
    free(data_B);

    return 0;
    }

组合模式(Composite Pattern)

组合模式类似于二叉树。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
typedef struct _Object
{
struct _Object** ppObject;
int number;
void (*operate)(struct _Object* pObject);

}Object;

void operate_of_parent(struct _Object* pObject)
{
int index;
assert(NULL != pObject);
assert(NULL != pObject->ppObject && 0 != pObject->number);

for(index = 0; index < pObject->number; index ++)
{
pObject->ppObject[index]->operate(pObject->ppObject[index]);
}
}

void operate_of_child(struct _Object* pObject)
{
assert(NULL != pObject);
printf("child node!\n");
}

void process(struct Object* pObject)
{
assert(NULL != pObject);
pObject->operate(pObject);
}

代码解释

你提供的代码实现了一个组合模式(Composite Pattern)。组合模式是一种结构型设计模式,它允许你将对象组合成树形结构来表示“部分-整体”的层次结构。组合模式使得客户端可以一致地处理单个对象和对象组合。

结构体定义

1
2
3
4
5
typedef struct _Object {
struct _Object** ppObject;
int number;
void (*operate)(struct _Object* pObject);
} Object;
  • struct _Object** ppObject;:这是一个指向对象数组的指针,用于存储子对象。
  • int number;:表示子对象的数量。
  • void (*operate)(struct _Object* pObject);:这是一个函数指针,指向一个操作函数。这个函数接受一个 struct _Object* 类型的参数。

    父对象的操作函数

    1
    2
    3
    4
    5
    6
    7
    8
    void operate_of_parent(struct _Object* pObject) {
    int index;
    assert(NULL != pObject);
    assert(NULL != pObject->ppObject && 0 != pObject->number);
    for(index = 0; index < pObject->number; index++) {
    pObject->ppObject[index]->operate(pObject->ppObject[index]);
    }
    }
  • operate_of_parent 函数负责遍历所有子对象,并调用每个子对象的 operate 函数。
  • assert 用于确保 pObjectppObject 不为 NULL,并且 number 不为 0

    子对象的操作函数

    1
    2
    3
    4
    void operate_of_child(struct _Object* pObject) {
    assert(NULL != pObject);
    printf("child node!\n");
    }
  • operate_of_child 函数是一个简单的操作函数,用于处理子对象。在这个示例中,它只是打印一条消息。

    处理函数

    1
    2
    3
    4
    void process(struct Object* pObject) {
    assert(NULL != pObject);
    pObject->operate(pObject);
    }
  • process 函数是一个通用的处理函数,它调用 pObjectoperate 函数来执行操作。

    组合模式的特点

  1. 树形结构
    • 组合模式允许你将对象组合成树形结构,表示“部分-整体”的层次关系。
    • 树的每个节点可以是叶子节点(没有子节点)或组合节点(有子节点)。
  2. 统一接口
    • 无论是叶子节点还是组合节点,它们都实现了相同的接口(在这个例子中是 operate 函数)。
    • 客户端可以一致地处理单个对象和对象组合,而不需要关心具体的实现。
  3. 灵活性
    • 可以轻松地添加、删除或修改树的结构,而不需要修改客户端代码。
    • 通过组合节点,可以递归地处理子节点。

      优点

  • 简化客户端代码:客户端可以一致地处理单个对象和对象组合,而不需要关心具体的实现。
  • 灵活性:可以轻松地添加、删除或修改树的结构,而不需要修改客户端代码。
  • 扩展性:可以通过添加新的叶子节点或组合节点来扩展系统功能。

    缺点

  • 复杂性:实现组合模式可能会增加代码的复杂性,特别是在处理复杂的树形结构时。
  • 性能开销:递归遍历树形结构可能会带来一定的性能开销。

    示例使用

    以下是一个示例程序,展示了如何使用组合模式来创建和处理对象树:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    #include <stdio.h>
    #include <stdlib.h>
    #include <assert.h>

    typedef struct _Object {
    struct _Object** ppObject;
    int number;
    void (*operate)(struct _Object* pObject);
    } Object;

    void operate_of_parent(struct _Object* pObject) {
    int index;
    assert(NULL != pObject);
    assert(NULL != pObject->ppObject && 0 != pObject->number);
    for(index = 0; index < pObject->number; index++) {
    pObject->ppObject[index]->operate(pObject->ppObject[index]);
    }
    }

    void operate_of_child(struct _Object* pObject) {
    assert(NULL != pObject);
    printf("child node!\n");
    }

    void process(struct Object* pObject) {
    assert(NULL != pObject);
    pObject->operate(pObject);
    }

    int main() {
    // 创建两个子对象
    Object child1 = {NULL, 0, operate_of_child};
    Object child2 = {NULL, 0, operate_of_child};

    // 创建一个父对象,并将其子对象添加到父对象中
    Object parent = {NULL, 2, operate_of_parent};
    parent.ppObject = (struct _Object**)malloc(parent.number * sizeof(struct _Object*));
    parent.ppObject[0] = &child1;
    parent.ppObject[1] = &child2;

    // 处理父对象
    process(&parent);

    // 释放分配的内存
    free(parent.ppObject);

    return 0;
    }

代码解释

  1. 创建子对象
    • child1child2 是两个子对象,它们的 ppObjectNULL,表示它们没有子对象。
    • number0,表示它们没有子对象。
    • operate 指向 operate_of_child 函数,表示它们的操作是打印一条消息。
  2. 创建父对象
    • parent 是一个父对象,它的 number2,表示它有两个子对象。
    • ppObject 是一个指向子对象数组的指针,需要动态分配内存。
    • operate 指向 operate_of_parent 函数,表示它的操作是遍历并调用每个子对象的 operate 函数。
  3. 处理父对象
    • 调用 process(&parent) 函数来处理父对象。
    • process 函数会调用 parentoperate 函数,即 operate_of_parent
    • operate_of_parent 函数会遍历 parent 的子对象,并调用每个子对象的 operate 函数,即 operate_of_child
  4. 释放内存
    • 使用 free 释放动态分配的内存,以避免内存泄漏。

运行结果

运行上述程序,输出将会是:

1
2
child node!
child node!

这表明父对象成功地调用了其所有子对象的 operate 函数,每个子对象都打印了一条消息。

总结

通过这个示例,我们可以看到组合模式如何帮助我们构建和处理树形结构。客户端可以一致地处理单个对象和对象组合,而不需要关心具体的实现。这使得代码更加灵活和可扩展。

模板方法模式(Template Method Pattern)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
typedef struct _Basic
{
void* pData;
void (*step1) (struct _Basic* pBasic);
void (*step2) (struct _Basic* pBasic);
void (*process) (struct _Basic* pBasic);
}Basic;

void process(struct _Basic* pBasic)
{
pBasic->step1(pBasic);
pBasic->step2(pBasic);
}

你提供的代码实现了一个模板方法模式(Template Method Pattern)。模板方法模式是一种行为设计模式,它定义了一个操作中的算法骨架,而将一些步骤延迟到子类中实现。模板方法使得子类可以在不改变算法结构的情况下重定义算法的某些特定步骤。

代码解释

结构体定义

1
2
3
4
5
6
typedef struct _Basic {
void* pData;
void (*step1)(struct _Basic* pBasic);
void (*step2)(struct _Basic* pBasic);
void (*process)(struct _Basic* pBasic);
} Basic;
  • void* pData;:这是一个指向数据的指针,可以用于存储任何类型的数据。
  • void (*step1)(struct _Basic* pBasic);:这是一个函数指针,指向一个名为 step1 的操作函数。
  • void (*step2)(struct _Basic* pBasic);:这是一个函数指针,指向一个名为 step2 的操作函数。
  • void (*process)(struct _Basic* pBasic);:这是一个函数指针,指向一个名为 process 的操作函数。

    模板方法

    1
    2
    3
    4
    void process(struct _Basic* pBasic) {
    pBasic->step1(pBasic);
    pBasic->step2(pBasic);
    }
  • process 函数是一个模板方法,它定义了算法的骨架。在这个例子中,算法的步骤是先调用 step1,再调用 step2
  • pBasic->step1(pBasic);:调用 step1 函数。
  • pBasic->step2(pBasic);:调用 step2 函数。

    模板方法模式的特点

  1. 算法骨架
    • 模板方法模式定义了一个操作的算法骨架,但将某些步骤的具体实现延迟到子类中。
    • 模板方法通常是一个最终方法(在C语言中无法直接实现,但可以通过约定来实现),以防止子类改变算法结构。
  2. 子类实现
    • 子类可以重定义算法的某些步骤,而不需要改变算法的结构。
    • 这使得子类可以在不改变整体算法的情况下,提供不同的实现。
  3. 灵活性
    • 模板方法模式使得算法的某些部分可以灵活地改变,而其他部分保持不变。
    • 这种模式可以用于实现框架,提供固定的算法结构,同时允许用户自定义某些步骤。

      优点

  4. 代码复用
    • 模板方法模式通过定义算法的骨架,使得公共部分的代码可以复用。
    • 子类只需要实现特定的步骤,而不需要重复实现公共部分。
  5. 扩展性
    • 子类可以轻松地扩展或修改算法的某些步骤,而不需要改变算法的整体结构。
    • 这使得代码更加灵活和可扩展。
  6. 控制反转
    • 模板方法模式通过将控制权交给子类,实现了控制反转。
    • 这使得子类可以在不改变算法结构的情况下,提供不同的实现。

      缺点

  7. 子类依赖
    • 子类必须实现模板方法中定义的某些步骤,这可能会增加子类的复杂性。
    • 如果子类没有正确实现这些步骤,可能会导致算法出错。
  8. 代码复杂性
    • 模板方法模式可能会增加代码的复杂性,特别是在处理复杂的算法时。
    • 需要仔细设计模板方法和子类之间的关系。

      示例使用

      以下是一个示例程序,展示了如何使用模板方法模式来实现一个简单的算法:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      #include <stdio.h>
      #include <stdlib.h>
      typedef struct _Basic {
      void* pData;
      void (*step1)(struct _Basic* pBasic);
      void (*step2)(struct _Basic* pBasic);
      void (*process)(struct _Basic* pBasic);
      } Basic;
      void step1(struct _Basic* pBasic) {
      printf("Step 1\n");
      }
      void step2(struct _Basic* pBasic) {
      printf("Step 2\n");
      }
      void process(struct _Basic* pBasic) {
      pBasic->step1(pBasic);
      pBasic->step2(pBasic);
      }
      int main() {
      // 创建一个 Basic 结构体实例
      Basic basic = {NULL, step1, step2, process};
      // 调用 process 函数
      basic.process(&basic);
      return 0;
      }

代码解释

  1. 定义 Basic 结构体
    • void* pData;:用于存储数据的指针,可以指向任何类型的数据。
    • void (*step1)(struct _Basic* pBasic);:指向 step1 函数的指针。
    • void (*step2)(struct _Basic* pBasic);:指向 step2 函数的指针。
    • void (*process)(struct _Basic* pBasic);:指向 process 函数的指针。
  2. 定义具体的步骤函数
    • step1 函数:模拟打开文件的操作。
    • step2 函数:模拟读取文件内容的操作。
  3. 定义模板方法
    • process 函数:定义了算法的骨架,先调用 step1,再调用 step2
  4. 主函数
    • 创建一个 Basic 结构体实例,并初始化 pDataNULLstep1step2 分别指向具体的步骤函数,process 指向模板方法。
    • 调用 process 函数,执行文件处理的算法。

      运行结果

      运行上述程序,输出将会是:
      1
      2
      Step 1: Open file
      Step 2: Read file content
      这表明模板方法模式成功地按照定义的算法骨架执行了文件处理的步骤。

进一步扩展

假设我们想扩展这个程序,增加一个关闭文件的步骤。我们可以定义一个新的步骤函数 step3,并修改 process 函数以包含这个新步骤。

新的步骤函数

1
2
3
void step3(struct _Basic* pBasic) {
printf("Step 3: Close file\n");
}

修改模板方法

1
2
3
4
5
void process(struct _Basic* pBasic) {
pBasic->step1(pBasic);
pBasic->step2(pBasic);
pBasic->step3(pBasic);
}

修改 Basic 结构体

我们需要在 Basic 结构体中添加一个指向 step3 函数的指针,并在 main 函数中初始化这个指针。

1
2
3
4
5
6
7
typedef struct _Basic {
void* pData;
void (*step1)(struct _Basic* pBasic);
void (*step2)(struct _Basic* pBasic);
void (*step3)(struct _Basic* pBasic);
void (*process)(struct _Basic* pBasic);
} Basic;

主函数

main 函数中,我们需要初始化 Basic 结构体的 step3 成员,并调用 process 函数。

1
2
3
4
5
6
7
8
9
int main() {
// 创建一个 Basic 结构体实例
Basic basic = {NULL, step1, step2, step3, process};

// 调用 process 函数
basic.process(&basic);

return 0;
}

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <stdio.h>
#include <stdlib.h>
typedef struct _Basic {
void* pData;
void (*step1)(struct _Basic* pBasic);
void (*step2)(struct _Basic* pBasic);
void (*step3)(struct _Basic* pBasic);
void (*process)(struct _Basic* pBasic);
} Basic;
void step1(struct _Basic* pBasic) {
printf("Step 1: Open file\n");
}
void step2(struct _Basic* pBasic) {
printf("Step 2: Read file content\n");
}
void step3(struct _Basic* pBasic) {
printf("Step 3: Close file\n");
}
void process(struct _Basic* pBasic) {
pBasic->step1(pBasic);
pBasic->step2(pBasic);
pBasic->step3(pBasic);
}
int main() {
// 创建一个 Basic 结构体实例
Basic basic = {NULL, step1, step2, step3, process};
// 调用 process 函数
basic.process(&basic);
return 0;
}

运行结果

运行上述程序,输出将会是:

1
2
3
Step 1: Open file
Step 2: Read file content
Step 3: Close file

这表明模板方法模式成功地按照定义的算法骨架执行了文件处理的步骤,包括打开文件、读取文件内容和关闭文件。

进一步扩展

模板方法模式的一个重要特性是它允许子类(或子结构体)在不改变算法结构的情况下,重新定义算法的某些特定步骤。在 C 语言中,我们可以通过创建不同的结构体实例来实现这一点。

假设我们有一个新的文件处理需求,需要在读取文件内容之前进行一些预处理。我们可以创建一个新的结构体实例,并重定义 step2 函数。

新的步骤函数

1
2
3
4
void step2_with_preprocessing(struct _Basic* pBasic) {
printf("Step 2: Preprocess file content\n");
printf("Step 2: Read file content\n");
}

主函数

main 函数中,我们可以创建一个新的 Basic 结构体实例,并初始化 step2 成员为 step2_with_preprocessing

1
2
3
4
5
6
7
8
9
10
11
int main() {
// 创建一个 Basic 结构体实例
Basic basic = {NULL, step1, step2, step3, process};
// 调用 process 函数
basic.process(&basic);
// 创建一个新的 Basic 结构体实例,重定义 step2
Basic basic_with_preprocessing = {NULL, step1, step2_with_preprocessing, step3, process};
// 调用 process 函数
basic_with_preprocessing.process(&basic_with_preprocessing);
return 0;
}

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include <stdio.h>
#include <stdlib.h>

typedef struct _Basic {
void* pData;
void (*step1)(struct _Basic* pBasic);
void (*step2)(struct _Basic* pBasic);
void (*step3)(struct _Basic* pBasic);
void (*process)(struct _Basic* pBasic);
} Basic;

void step1(struct _Basic* pBasic) {
printf("Step 1: Open file\n");
}

void step2(struct _Basic* pBasic) {
printf("Step 2: Read file content\n");
}

void step3(struct _Basic* pBasic) {
printf("Step 3: Close file\n");
}

void step2_with_preprocessing(struct _Basic* pBasic) {
printf("Step 2: Preprocess file content\n");
printf("Step 2: Read file content\n");
}

void process(struct _Basic* pBasic) {
pBasic->step1(pBasic);
pBasic->step2(pBasic);
pBasic->step3(pBasic);
}

int main() {
// 创建一个 Basic 结构体实例
Basic basic = {NULL, step1, step2, step3, process};

// 调用 process 函数
printf("Standard file processing:\n");
basic.process(&basic);

// 创建一个新的 Basic 结构体实例,重定义 step2
Basic basic_with_preprocessing = {NULL, step1, step2_with_preprocessing, step3, process};

// 调用 process 函数
printf("\nFile processing with preprocessing:\n");
basic_with_preprocessing.process(&basic_with_preprocessing);

return 0;
}

代码解释

  1. 定义 Basic 结构体
    • void* pData;:用于存储数据的指针,可以指向任何类型的数据。
    • void (*step1)(struct _Basic* pBasic);:指向 step1 函数的指针。
    • void (*step2)(struct _Basic* pBasic);:指向 step2 函数的指针。
    • void (*step3)(struct _Basic* pBasic);:指向 step3 函数的指针。
    • void (*process)(struct _Basic* pBasic);:指向 process 函数的指针。
  2. 定义具体的步骤函数
    • step1 函数:模拟打开文件的操作。
    • step2 函数:模拟读取文件内容的操作。
    • step3 函数:模拟关闭文件的操作。
    • step2_with_preprocessing 函数:模拟在读取文件内容之前进行预处理的操作。
  3. 定义模板方法
    • process 函数:定义了算法的骨架,先调用 step1,再调用 step2,最后调用 step3
  4. 主函数
    • 创建一个 Basic 结构体实例 basic,并初始化 pDataNULLstep1step2step3 分别指向具体的步骤函数,process 指向模板方法。
    • 调用 basic.process(&basic),执行标准的文件处理过程。
    • 创建一个新的 Basic 结构体实例 basic_with_preprocessing,并初始化 pDataNULLstep1step3 分别指向具体的步骤函数,step2 指向 step2_with_preprocessingprocess 指向模板方法。
    • 调用 basic_with_preprocessing.process(&basic_with_preprocessing),执行带有预处理的文件处理过程。

运行结果

运行上述程序,输出将会是:

1
2
3
4
5
6
7
8
9
Standard file processing:
Step 1: Open file
Step 2: Read file content
Step 3: Close file
File processing with preprocessing:
Step 1: Open file
Step 2: Preprocess file content
Step 2: Read file content
Step 3: Close file

这表明模板方法模式成功地按照定义的算法骨架执行了文件处理的步骤,并且可以通过重定义特定步骤来实现不同的处理逻辑。

总结

通过这个示例,我们可以看到模板方法模式在 C 语言中的应用。它允许我们在不改变算法结构的情况下,通过重定义特定步骤来实现不同的功能。

工厂模式(Factory Pattern)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// 工厂
typedef struct _Shoe
{
int type;
void (*print_shoe)(struct _Shoe*);
}Shoe;

// 生产胶鞋和皮鞋
void print_leather_shoe(struct _Shoe* pShoe)
{
assert(NULL != pShoe);
printf("This is a leather show!\n");
}

void print_rubber_shoe(struct _Shoe* pShoe)
{
assert(NULL != pShoe);
printf("This is a rubber shoe!\n");
}


// 生产(工厂方法)
#define LEATHER_TYPE 0x01
#define RUBBER_TYPE 0x02

Shoe* manufacture_new_shoe(int type)
{
assert(LEATHER_TYPE == type || RUBBER_TYPE == type);

Shoe* pShoe = (Shoe*)malloc(sizeof(Shoe)); // 使用 malloc 分配内存
assert(NULL != pShoe);

memset(pShoe, 0, sizeof(Shoe)); // 并将内存初始化为0初始化为零
if(LEATHER_TYPE == type)
{
pShoe->type == LEATHER_TYPE;
pShoe->print_shoe = print_leather_shoe;
}
else
{
pShoe->type == RUBBER_TYPE;
pShoe->print_shoe = print_rubber_shoe;
}

return pShoe;
}

应用

完成 main 函数的示例:

1
2
3
4
5
6
7
8
9
10
11
12
int main()
{
Shoe* leatherShoe = manufacture_new_shoe(LEATHER_TYPE);
leatherShoe->print_shoe(leatherShoe);
free(leatherShoe);

Shoe* rubberShoe = manufacture_new_shoe(RUBBER_TYPE);
rubberShoe->print_shoe(rubberShoe);
free(rubberShoe);

return 0;
}

代码解释

  1. 创建皮鞋
    • Shoe* leatherShoe = manufacture_new_shoe(LEATHER_TYPE);:调用工厂方法 manufacture_new_shoe,传入 LEATHER_TYPE,创建一个皮鞋对象。
    • leatherShoe->print_shoe(leatherShoe);:调用皮鞋对象的 print_shoe 方法,打印“这是皮鞋”的信息。
    • free(leatherShoe);:释放皮鞋对象占用的内存。
  2. 创建胶鞋
    • Shoe* rubberShoe = manufacture_new_shoe(RUBBER_TYPE);:调用工厂方法 manufacture_new_shoe,传入 RUBBER_TYPE,创建一个胶鞋对象。
    • rubberShoe->print_shoe(rubberShoe);:调用胶鞋对象的 print_shoe 方法,打印“这是胶鞋”的信息。
    • free(rubberShoe);:释放胶鞋对象占用的内存。

运行结果

当你编译并运行上述代码时,输出将会是:

1
2
This is a leather shoe!
This is a rubber shoe!

总结

通过工厂模式,我们可以将对象的创建逻辑集中管理,使得代码更加灵活和可扩展。客户端代码只需要调用工厂方法,而不需要关心对象的具体创建过程,这提高了代码的可维护性和可扩展性。

抽象工厂模式(Abstract Factory Pattern)

前面我们写过的工厂模式实际上是对产品的抽象。对于不同的用户需求,我们可以给予不同的产品,而且这些产品的接口都是一致的。而抽象工厂呢?顾名思义,就是说我们的工厂是不一定的。怎么理解呢,举个例子。

假设有两个水果店都在卖水果,都卖苹果和葡萄。其中一个水果店买白苹果和白葡萄,另外一个水果店卖红苹果和红葡萄。所以说,对于水果店而言,尽管都在卖水果,但是两个店卖的品种不一样。 

定义(抽象)水果

1
2
3
4
5
6
7
8
9
typedef struct _Apple
{
void (*print_apple)();
}Apple;

typedef struct _Grape
{
void (*print_grape)();
}Grape;

定义具体的水果方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void print_white_apple()
{
printf("white apple!\n");
}

void print_red_apple()
{
printf("red apple!\n");
}

void print_white_grape()
{
printf("white grape!\n");
}

void print_red_grape()
{
printf("red grape!\n");
}

定义抽象工厂

1
2
3
4
5
typedef struct _FruitShop
{
Apple* (*sell_apple)();
Apple* (*sell_grape)();
}FruitShop;

定义具体的水果店

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Apple* sell_white_apple()
{
Apple* pApple = (Apple*) malloc(sizeof(Apple));
assert(NULL != pApple);

pApple->print_apple = print_white_apple;
return pApple;
}

Grape* sell_white_grape()
{
Grape* pGrape = (Grape*) malloc(sizeof(Grape));
assert(NULL != pGrape);

pGrape->print_grape = print_white_grape;
return pGrape;
}

创建工厂

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
FruitShop* create_fruit_shop(int color)
{
FruitShop* pFruitShop = (FruitShop*) malloc(sizeof(FruitShop));
assert(NULL != pFruitShop);

if(WHITE == color)
{
pFruitShop->sell_apple = sell_white_apple;
pFruitShop->sell_grape = sell_white_grape;
}
else
{
pFruitShop->sell_apple = sell_red_apple;
pFruitShop->sell_grape = sell_red_grape;
}

return pFruitShop;
}

完整代码

包括创建工厂、使用工厂创建具体的水果,并调用打印方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
// 定义抽象水果
typedef struct _Apple
{
void (*print_apple)();
} Apple;
typedef struct _Grape
{
void (*print_grape)();
} Grape;
// 定义具体的水果方法
void print_white_apple()
{
printf("white apple!\n");
}
void print_red_apple()
{
printf("red apple!\n");
}
void print_white_grape()
{
printf("white grape!\n");
}
void print_red_grape()
{
printf("red grape!\n");
}


// 定义抽象工厂
typedef struct _FruitShop
{
Apple* (*sell_apple)();
Grape* (*sell_grape)();
} FruitShop;
// 定义具体的水果店方法
Apple* sell_white_apple()
{
Apple* pApple = (Apple*) malloc(sizeof(Apple));
assert(NULL != pApple);
pApple->print_apple = print_white_apple;
return pApple;
}
Grape* sell_white_grape()
{
Grape* pGrape = (Grape*) malloc(sizeof(Grape));
assert(NULL != pGrape);
pGrape->print_grape = print_white_grape;
return pGrape;
}
Apple* sell_red_apple()
{
Apple* pApple = (Apple*) malloc(sizeof(Apple));
assert(NULL != pApple);
pApple->print_apple = print_red_apple;
return pApple;
}
Grape* sell_red_grape()
{
Grape* pGrape = (Grape*) malloc(sizeof(Grape));
assert(NULL != pGrape);
pGrape->print_grape = print_red_grape;
return pGrape;
}



// 创建工厂
#define WHITE 0x01
#define RED 0x02
FruitShop* create_fruit_shop(int color)
{
FruitShop* pFruitShop = (FruitShop*) malloc(sizeof(FruitShop));
assert(NULL != pFruitShop);
if (WHITE == color)
{
pFruitShop->sell_apple = sell_white_apple;
pFruitShop->sell_grape = sell_white_grape;
}
else
{
pFruitShop->sell_apple = sell_red_apple;
pFruitShop->sell_grape = sell_red_grape;
}
return pFruitShop;
}


// 主函数
int main()
{
// 创建一个卖白色水果的水果店
FruitShop* whiteFruitShop = create_fruit_shop(WHITE);
Apple* whiteApple = whiteFruitShop->sell_apple();
Grape* whiteGrape = whiteFruitShop->sell_grape();
// 创建一个卖红色水果的水果店
FruitShop* redFruitShop = create_fruit_shop(RED);
Apple* redApple = redFruitShop->sell_apple();
Grape* redGrape = redFruitShop->sell_grape();
// 打印水果信息
whiteApple->print_apple();
whiteGrape->print_grape();
redApple->print_apple();
redGrape->print_grape();
// 释放内存
free(whiteApple);
free(whiteGrape);
free(redApple);
free(redGrape);
free(whiteFruitShop);
free(redFruitShop);
return 0;
}

代码解释

  1. 定义抽象水果:定义了 AppleGrape 结构体,每个结构体包含一个函数指针,用于打印水果的信息。
  2. 定义具体的水果方法:定义了四个具体的水果方法,分别打印不同颜色的苹果和葡萄。
  3. 定义抽象工厂:定义了 FruitShop 结构体,包含两个函数指针,分别用于创建苹果和葡萄。
  4. 定义具体的水果店方法:定义了四个具体的水果店方法,分别创建不同颜色的苹果和葡萄。
  5. 创建工厂:定义了 create_fruit_shop 函数,根据传入的颜色参数创建一个具体的水果店。
  6. 主函数
    • 创建一个卖白色水果的水果店。
    • 创建一个卖红色水果的水果店。
    • 通过水果店的方法创建具体的苹果和葡萄。
    • 打印水果信息。
    • 释放分配的内存。
      通过这种方式,你可以在不修改客户端代码的情况下,轻松地添加新的水果类型或颜色。

责任链模式

AI:

责任链模式是什么来着?我记得它是一种设计模式,允许将请求沿着一条链传递,每个处理对象都有机会处理这个请求。这样可以避免请求的发送者和接收者之间的耦合,让系统更灵活。

那责任链模式的结构是怎样的呢?通常有一个处理者接口,每个处理者都有一个下一个处理者的引用。处理者可能会处理请求,或者将请求传递给下一个处理者。这样形成一个链式结构。

示例1

参考文献

责任链模式是很实用的一种实际方法。举个例子来说,我们平常在公司里面难免不了报销流程。但是,我们知道公司里面每一级的领导的报批额度是不一样的。比如说,科长的额度是1000元,部长是10000元,总经理是10万元。

那么这个时候,我们应该怎么设计呢?其实可以这么理解。比如说,有人来找领导报销费用了,那么领导可以自己先看看自己能不能报。如果费用可以顺利报下来当然最好,可是万一报不下来呢?那就只能请示领导的领导了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#include <assert.h>
#include <stdio.h>

typedef struct _Leader {
struct _Leader* next;
int account;
int (*request)(struct _Leader* pLeader, int num);
} Leader;

// 我们首先需要设置额度和领导
void set_account(struct _Leader* pLeader, int account) {
assert(pLeader != NULL);
pLeader->account = account;
}

void set_next_leader(struct _Leader* pLeader, struct _Leader* next) {
assert(pLeader != NULL && next != NULL);
pLeader->next = next;
}

// 此时,如果有一个员工过来报销费用,那么应该怎么做呢?
// 假设此时的Leader是经理,报销额度是10万元。
// 所以此时,我们可以看看报销的费用是不是小于10万元?
// 少于这个数就OK,反之就得上报自己的领导了。
int request_handler(struct _Leader* pLeader, int num) {
assert(pLeader != NULL && num != 0);
if (num <= pLeader->account) {
return 1;
} else if (pLeader->next != NULL) {
return pLeader->next->request(pLeader->next, num);
} else {
return 0;
}
}

int main() {
Leader section_head, department_head, manager;

// 初始化各领导的账户和下一个处理人
set_account(&section_head, 1000);
set_account(&department_head, 10000);
set_account(&manager, 100000);

set_next_leader(&section_head, &department_head);
set_next_leader(&department_head, &manager);
manager.next = NULL;

// 设置统一的请求处理函数
section_head.request = request_handler;
department_head.request = request_handler;
manager.request = request_handler;

// 测试报销金额
int amount = 5000;
int result = section_head.request(&section_head, amount);

if (result) {
printf("报销成功\n");
} else {
printf("报销失败\n");
}

return 0;
}

示例2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#include <stdio.h>

struct Request {
int type;
};

struct Handler {
void (*handle)(struct Handler*, struct Request*);
struct Handler* next;
};

void handlerA_handle(struct Handler* handler, struct Request* req) {
if (req->type == 1) {
printf("Handler A 处理请求类型1\n");
} else {
if (handler->next != NULL) {
handler->next->handle(handler->next, req);
} else {
printf("没有处理器能处理该请求\n");
}
}
}

void handlerB_handle(struct Handler* handler, struct Request* req) {
if (req->type == 2) {
printf("Handler B 处理请求类型2\n");
} else {
if (handler->next != NULL) {
handler->next->handle(handler->next, req);
} else {
printf("没有处理器能处理该请求\n");
}
}
}

void handlerC_handle(struct Handler* handler, struct Request* req) {
if (req->type == 3) {
printf("Handler C 处理请求类型3\n");
} else {
if (handler->next != NULL) {
handler->next->handle(handler->next, req);
} else {
printf("没有处理器能处理该请求\n");
}
}
}

int main() {
struct Handler handlerA = { handlerA_handle, NULL };
struct Handler handlerB = { handlerB_handle, NULL };
struct Handler handlerC = { handlerC_handle, NULL };

handlerA.next = &handlerB;
handlerB.next = &handlerC;
handlerC.next = NULL;

struct Request req1 = {1};
struct Request req2 = {2};
struct Request req3 = {3};
struct Request req4 = {4};

printf("处理请求类型1:\n");
handlerA.handle(&handlerA, &req1);

printf("\n处理请求类型2:\n");
handlerA.handle(&handlerA, &req2);

printf("\n处理请求类型3:\n");
handlerA.handle(&handlerA, &req3);

printf("\n处理请求类型4:\n");
handlerA.handle(&handlerA, &req4);

return 0;
}

迭代器模式

C语言迭代器模式示例

迭代器模式是一种行为设计模式,允许你以顺序一致的方式遍历聚合对象中的各个元素,而无需暴露其内部表示。在C语言中,由于没有内置的集合类,因此需要手动实现迭代器模式。

以下是一个简单的C语言迭代器模式示例:

1. 定义集合结构

首先,定义一个表示集合的结构体。这里我们使用一个简单的数组作为集合。

1
2
3
4
typedef struct {
int* data; // 存储元素的数组
int length; // 数组的长度
} Collection;

2. 定义迭代器结构

然后,定义一个表示迭代器的结构体。迭代器需要知道当前的位置(索引)以及它正在遍历的集合。

1
2
3
4
typedef struct {
Collection* collection; // 指向正在遍历的集合
int current_index; // 当前索引
} Iterator;

3. 初始化迭代器函数

创建一个函数来初始化迭代器。这个函数将接受一个集合和一个起始索引,并返回一个初始化好的迭代器。

1
2
3
4
5
6
Iterator* createIterator(Collection* collection, int start_index) {
Iterator* iterator = (Iterator*)malloc(sizeof(Iterator));
iterator->collection = collection;
iterator->current_index = start_index;
return iterator;
}

4. 检查是否还有下一个元素

创建一个函数来检查迭代器是否还有下一个元素可以访问。

1
2
3
int hasNext(Iterator* iterator) {
return iterator->current_index < iterator->collection->length;
}

5. 获取下一个元素

创建一个函数来获取当前元素,并将迭代器移动到下一个位置。

1
2
3
4
5
int next(Iterator* iterator) {
int value = iterator->collection->data[iterator->current_index];
iterator->current_index++;
return value;
}

6. 释放迭代器

创建一个函数来释放迭代器占用的内存。

1
2
3
void releaseIterator(Iterator* iterator) {
free(iterator);
}

7. 主函数

在主函数中,创建一个集合,初始化迭代器,并遍历集合中的元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
int main() {
// 创建并初始化集合
Collection* collection = (Collection*)malloc(sizeof(Collection));
collection->length = 5;
collection->data = (int*)malloc(collection->length * sizeof(int));
collection->data[0] = 1;
collection->data[1] = 2;
collection->data[2] = 3;
collection->data[3] = 4;
collection->data[4] = 5;
// 创建迭代器
Iterator* iterator = createIterator(collection, 0);

// 遍历集合
while (hasNext(iterator)) {
int value = next(iterator);
printf("%d ", value);
}

// 释放资源
releaseIterator(iterator);
free(collection->data);
free(collection);

return 0;
}

输出

运行这个程序,你会看到以下输出:

1
1 2 3 4 5

解释

  • 集合结构 (Collection):表示存储元素的容器。在这里,我们使用一个整数数组来存储元素。
  • 迭代器结构 (Iterator):包含指向集合的指针和当前索引。索引用于跟踪当前遍历的位置。
  • 初始化函数 (createIterator):创建并初始化迭代器,设置起始索引。
  • 检查函数 (hasNext):检查是否还有下一个元素可以访问。
  • 获取函数 (next):返回当前元素,并将索引递增以指向下一个元素。
  • 释放函数 (releaseIterator):释放迭代器占用的内存。

通过这个示例,你可以看到迭代器模式如何封装集合的遍历逻辑,使得客户端代码不需要知道集合的内部结构即可遍历其中的元素。

外观模式

说明

外观模式(Facade Pattern)为子系统中的一组接口提供一个统一的接口,使得子系统更容易使用

注:子系统为同级别

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#include <stdio.h>

// 子系统1:CPU
typedef struct {
void (*freeze)(void);
void (*jump)(long position);
void (*execute)(void);
} CPU;

void cpu_freeze() {
printf("CPU freeze\n");
}

void cpu_jump(long position) {
printf("CPU jump to %ld\n", position);
}

void cpu_execute() {
printf("CPU execute\n");
}

// 子系统2:内存
typedef struct {
void (*load)(long position, char *data);
} Memory;

void memory_load(long position, char *data) {
printf("Memory load data '%s' to position %ld\n", data, position);
}

// 子系统3:硬盘
typedef struct {
char* (*read)(long lba, int size);
} HardDrive;

char* harddrive_read(long lba, int size) {
static char data[100];
snprintf(data, sizeof(data), "HardDrive read data from LBA %ld, size %d", lba, size);
return data;
}

// 外观:计算机
typedef struct {
CPU *cpu;
Memory *memory;
HardDrive *harddrive;
} Computer;

void computer_start(Computer *computer) {
char *boot_data = computer->harddrive->read(0, 1024);
computer->memory->load(0, boot_data);
computer->cpu->freeze();
computer->cpu->jump(0);
computer->cpu->execute();
}

int main() {
// 初始化子系统
CPU cpu = {cpu_freeze, cpu_jump, cpu_execute};
Memory memory = {memory_load};
HardDrive harddrive = {harddrive_read};

// 创建外观
Computer computer = {&cpu, &memory, &harddrive};

// 使用外观简化接口
computer_start(&computer);

return 0;
}

这个例子展示了如何使用外观模式简化复杂系统的使用,客户端只需要调用computer_start()而不需要关心CPU、内存和硬盘之间的复杂交互。

输出结果:

1
2
3
4
5
HardDrive read data from LBA 0, size 1024
Memory load data 'HardDrive read data from LBA 0, size 1024' to position 0
CPU freeze
CPU jump to 0
CPU execute

代理模式

代理模式(Proxy Pattern) : 通过代理来访问真实的对象

保护代理示例(控制访问权限)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <stdio.h>
#include <stdbool.h>

// 抽象主题接口
typedef struct {
void (*request)(void);
} Subject;

// 真实主题
void real_request() {
printf("RealSubject: Handling request.\n");
}

// 代理
typedef struct {
Subject real_subject;
bool check_access() {
// 这里简化了权限检查逻辑
printf("Proxy: Checking access prior to firing a real request.\n");
return true;
}
void log_access() {
printf("Proxy: Logging the time of request.\n");
}
} Proxy;

void proxy_request(Proxy *proxy) {
if (proxy->check_access()) {
proxy->real_subject.request();
proxy->log_access();
}
}

int main() {
// 创建真实主题和代理
Subject real = {real_request};
Proxy proxy = {real};

// 客户端通过代理访问
printf("Client: Executing the client code with a proxy:\n");
proxy_request(&proxy);

return 0;
}

虚拟代理示例(延迟加载)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <stdio.h>
#include <stdlib.h>

// 大对象接口
typedef struct {
void (*display)(void);
} Image;

// 真实大对象
void real_image_display() {
printf("Displaying real image (expensive operation)\n");
}

// 虚拟代理
typedef struct {
Image *real_image;
int loaded;
} ImageProxy;

void proxy_image_display(ImageProxy *proxy) {
if (!proxy->loaded) {
printf("Proxy: Loading real image...\n");
proxy->real_image = (Image *)malloc(sizeof(Image));
proxy->real_image->display = real_image_display;
proxy->loaded = 1;
}
proxy->real_image->display();
}

int main() {
// 创建代理,初始时不加载真实对象
ImageProxy proxy = {NULL, 0};

// 首次访问时才开始加载真实对象
printf("Client: First access to image (will load):\n");
proxy_image_display(&proxy);

printf("\nClient: Second access (no loading needed):\n");
proxy_image_display(&proxy);

// 释放资源
if (proxy.real_image) free(proxy.real_image);

return 0;
}

代码说明

  1. 保护代理示例
    • 代理在转发请求前执行访问控制
    • 记录请求日志
    • 客户端只与代理交互,不知道真实对象
  2. 虚拟代理示例
    • 延迟创建和加载昂贵的真实对象
    • 第一次访问时加载真实对象
    • 后续访问直接使用已加载的对象
  3. 代理模式优点
    • 控制对真实对象的访问
    • 作为真实对象的替身,可以在访问对象时添加额外功能
    • 实现延迟加载,提高系统性能

输出结果(保护代理):

1
2
3
4
Client: Executing the client code with a proxy:
Proxy: Checking access prior to firing a real request.
RealSubject: Handling request.
Proxy: Logging the time of request.

输出结果(虚拟代理):

1
2
3
4
5
6
Client: First access to image (will load):
Proxy: Loading real image...
Displaying real image (expensive operation)

Client: Second access (no loading needed):
Displaying real image (expensive operation)

享元模式

参考文献

[1] C语言和设计模式(总结篇) 用了多年的C

[2] C语言实现C++的封装继承与多态