组合模式
1 简介
组合模式将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。它应用于具有树形结构的对象组合。
优点就是:用一套调用方式处理对象组合中各个对象,且允许自由地增加对象个数。
2 模拟场景
文件系统的目录结构解释一个非常典型的目录结构。
本文假设有如下形式的文件系统,并以此为例:
3 使用组合模式实现文件系统
参与者
Component: FileSystemNode
为文件系统中的节点声明接口
Leaf: File
文件对象,在文件系统中,文件对象是文件系统的叶子对象(即:不可能再为文件对象添加子节点)
Composite:Folder
文件夹对象
UML
FileSystemNode示例代码
file_system_node.h
#ifndef FILE_SYSTEM_NODE_H
#define FILE_SYSTEM_NODE_H
struct FileSystemNode {
char nodeName[100];
struct FileSystemNode *childList[100];
void (*Operation)(struct FileSystemNode *this);
void (*Add)(struct FileSystemNode *this, struct FileSystemNode *node);
void (*Remove)(struct FileSystemNode *this, struct FileSystemNode *node);
};
#endif
File示例代码
file.h
#ifndef FILE_H
#define FILE_H
#include "file_system_node.h"
// 构造函数
void File(struct FileSystemNode *this, char *nodeName);
// 析构函数
void _File(struct FileSystemNode *this);
#endif
file.c
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "file.h"
static void Operation(struct FileSystemNode *this)
{
printf("操作文件 %s\n", this->nodeName);
}
static void Add(struct FileSystemNode *this, struct FileSystemNode *node)
{
printf("error: 文件节点,不支持增加子节点\n");
}
static void Remove(struct FileSystemNode *this, struct FileSystemNode *node)
{
printf("error: 文件节点,不支持删除子节点\n");
}
// 构造函数
void File(struct FileSystemNode *this, char *nodeName)
{
strcpy(this->nodeName, nodeName);
memset(this->childList, 0, sizeof(this->childList));
this->Operation = Operation;
this->Add = Add;
this->Remove = Remove;
}
// 析构函数
void _File(struct FileSystemNode *this)
{
memset(this->nodeName, 0, sizeof(this->nodeName));
memset(this->childList, 0, sizeof(this->childList));
this->Operation = NULL;
this->Add = NULL;
this->Remove = NULL;
}
Folder示例代码
folder.h
#ifndef FOLDER_H
#define FOLDER_H
#include "file_system_node.h"
// 构造函数
void Folder(struct FileSystemNode *this, char *nodeName);
// 析构函数
void _Folder(struct FileSystemNode *this);
#endif
folder.c
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "folder.h"
static void Operation(struct FileSystemNode *this)
{
printf("操作文件夹 %s\n", this->nodeName);
int i;
for (i = 0; i < 100; i++) {
if (this->childList[i] != NULL) {
this->childList[i]->Operation(this->childList[i]);
}
}
}
static void Add(struct FileSystemNode *this, struct FileSystemNode *node)
{
int i;
for (i = 0; i < 100; i++) {
if (this->childList[i] == NULL) {
this->childList[i] = node;
return;
}
}
printf("error: add node fail\n");
}
static void Remove(struct FileSystemNode *this, struct FileSystemNode *node)
{
int i;
for (i = 0; i < 100; i++) {
if (this->childList[i] == node) {
this->childList[i] = NULL;
return;
}
}
printf("error: remove node fail\n");
}
// 构造函数
void Folder(struct FileSystemNode *this, char *nodeName)
{
strcpy(this->nodeName, nodeName);
memset(this->childList, 0, sizeof(this->childList));
this->Operation = Operation;
this->Add = Add;
this->Remove = Remove;
}
// 析构函数
void _Folder(struct FileSystemNode *this)
{
memset(this->nodeName, 0, sizeof(this->nodeName));
memset(this->childList, 0, sizeof(this->childList));
this->Operation = NULL;
this->Add = NULL;
this->Remove = NULL;
}
客户端代码示例
#include "folder.h"
#include "file.h"
void main()
{
struct FileSystemNode root;
struct FileSystemNode folder1;
struct FileSystemNode folder2;
struct FileSystemNode folder3;
struct FileSystemNode file1;
struct FileSystemNode file2;
struct FileSystemNode file3;
Folder(&root, "root");
Folder(&folder1, "folder1");
Folder(&folder2, "folder2");
Folder(&folder3, "folder3");
File(&file1, "file1");
File(&file2, "file2");
File(&file3, "file3");
root.Add(&root, &folder1);
root.Add(&root, &folder2);
root.Add(&root, &file1);
folder1.Add(&folder1, &file2);
folder1.Add(&folder1, &file3);
folder2.Add(&folder2, &folder3);
root.Operation(&root);
}
客户端显示示例
操作文件夹 root
操作文件夹 folder1
操作文件 file2
操作文件 file3
操作文件夹 folder2
操作文件夹 folder3
操作文件 file1
4 组合模式的透明性与安全性分析
从上面的UML和示例代码不难发现,对于File,Add()和Remove()是没有意义的,甚至可以说是错误的。虽然我们为File实现的Add()和Remove()为空,并打印了错误信息,但这仍然可能引入错误(企图向File增加子节点本身就是一种错误)。
那么,如何解决这个问题呢?
解决思路是:既然File不需要Add()和Remove(),那我们就应该将Add()和Remove()从FileSystemNode中移到Folder中。
但这又带来了一个新的问题:客户端调用File和Folder时不能采用完全一致的方法,因为他们提供的方法并不完全一致。
上述两种方案各有优势,但都不完美。没有一个同时解决上述两个问题的完美方案。
对于前一种方案,因为具有透明性(客户端可以不区分File和Folder),我们称其为透明的组合模式。
对于后一种方案,因为更加安全(客户端不会错误地调用无意义的方法),我们称其为安全的组合模式。
通常我们根据场景对透明性和安全性的要求,以及个人喜好,来选择方案。
以下给出安全的组合模式的UML,以便和透明的组合模式进行对比:
使用安全的组合模式时,客户端需要通过GetType(),获知节点是File还是Folder,只有当节点为Folder是才能调用Add()和Remove()。