组合模式(Composite Pattern)

我们PC用到的文件系统,其实就是我们数据结构里的树形结构,我们处理树中的每个节点时,其实不用考虑他是叶子节点还是根节点,因为他们的成员函数都是一样的,这个就是组合模式的精髓。他模糊了简单元素和复杂元素的概念,客户程序可以向处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素的内部结构解耦。

组合模式: 允许你将对象组合成树形结构来表现『整体/部分』的层次结构。组合能让客户以一致的方式处理个别对象以及对象组合。

抽象构件(Component): 这是一个抽象角色,它给参加组合的对象定义出公共的接口以及默认行为,可以用来管理所有的子对象。在安全式的组合模式里,构件角色并不是定义出管理子对象的方法,这一定义由树枝构件对象给出。
树叶构件(Leaf) : 树叶对象是没有下级子对象的方法,定义出参加组合的原始对象的行为。
树枝构建(Compositic): 代表参加组合的有下级子对象的对象。树枝对象给出所有的管理子对象的方法,比如add(),remove(),getChild()等。

组合模式的实现根据所实现接口的区别分为两种形式,分别称为安全模式和透明模式。组合模式可以不提供父对象的管理方法,但组合模式必须在合适的地方提供子对象的管理方法(诸如:add、remove、getChild等)。

透明模式: 在Component里面声明所有的用来管理子类对象的方法,包括add()、remove(),以及getChild()方法。这样做的好处是所有的构件类都有相同的接口。在客户端看来,树叶类对象与合成类对象的区别起码在接口层次上消失了,客户端可以同等同的对待所有的对象。这就是透明形式的组合模式。

这个选择的缺点是不够安全,因为树叶类对象和合成类对象在本质上是有区别的。树叶类对象不可能有下一个层次的对象,因此add()、remove()以及getChild()方法没有意义,是在编译时期不会出错,而只会在运行时期才会出错或者说识别出来。针对这个,我们常常用throw 抛出异常。

安全方式: 是在Composite类里面声明所有的用来管理子类对象的方法。这样的做法是安全的做法,因为树叶类型的对象根本就没有管理子类对象的方法,因此,如果客户端对树叶类对象使用这些方法时,程序会在编译时期出错。
这个选择的缺点是不够透明,因为树叶类和合成类将具有不同的接口。

上边的图安全方式的组合模式的类图结构和样例实现,透明方式就是在叶子节点的add()/remove()/GetChild()均有实现,不过是无意义的实现。大部分应用都是基于透明模式的,因为这样代码可以重用。

下面看一个实例:

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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
//Menu.h
#include <string>
class Menu
{
public:
virtual ~Menu();
virtual void Add(Menu*);
virtual void Remove(Menu*);
virtual Menu* GetChild(int);
virtual void Display() = 0;
protected:
Menu();
Menu(std::string);
std::string m_strName;
};
//Menu.cpp
#include "stdafx.h"
#include "Menu.h"
Menu::Menu()
{
}
Menu::Menu(std::string strName) : m_strName(strName)
{
}
Menu::~Menu()
{
}
void Menu::Add(Menu* pMenu)
{}
void Menu::Remove(Menu* pMenu)
{}
Menu* Menu::GetChild(int index)
{
return NULL;
}
//SubMenu.h
#include "Menu.h"
class SubMenu : public Menu
{
public:
SubMenu();
SubMenu(std::string);
virtual ~SubMenu();
void Display();
};
//SubMenu.cpp
#include "stdafx.h"
#include "SubMenu.h"
#include <iostream>
using namespace std;
SubMenu::SubMenu()
{
}
SubMenu::SubMenu(string strName) : Menu(strName)
{
}
SubMenu::~SubMenu()
{
}
void SubMenu::Display()
{
cout << m_strName << endl;
}
//CompositMenu.h
#include "Menu.h"
#include <vector>
class CompositMenu : public Menu
{
public:
CompositMenu();
CompositMenu(std::string);
virtual ~CompositMenu();
void Add(Menu*);
void Remove(Menu*);
Menu* GetChild(int);
void Display();
private:
std::vector<Menu*> m_vMenu;
};
//CompositMenu.cpp
#include "stdafx.h"
#include "CompositMenu.h"
#include <iostream>
using namespace std;
CompositMenu::CompositMenu()
{
}
CompositMenu::CompositMenu(string strName) : Menu(strName)
{
}
CompositMenu::~CompositMenu()
{
}
void CompositMenu::Add(Menu* pMenu)
{
m_vMenu.push_back(pMenu);
}
void CompositMenu::Remove(Menu* pMenu)
{
m_vMenu.erase(&pMenu);
}
Menu* CompositMenu::GetChild(int index)
{
return m_vMenu[index];
}
void CompositMenu::Display()
{
cout << "+" << m_strName << endl;
vector<Menu*>::iterator it = m_vMenu.begin();
for (; it != m_vMenu.end(); ++it)
{
cout << "|-";
(*it)->Display();
}
}
#include "stdafx.h"
#include "Menu.h"
#include "SubMenu.h"
#include "CompositMenu.h"
int main(int argc, char* argv[])
{
Menu* pMenu = new CompositMenu("国内新闻");
pMenu->Add(new SubMenu("时事新闻"));
pMenu->Add(new SubMenu("社会新闻"));
pMenu->Display();
pMenu = new CompositMenu("国际新闻");
pMenu->Add(new SubMenu("国际要闻"));
pMenu->Add(new SubMenu("环球视野"));
pMenu->Display();
return 0;
}

几个要点:

状态模式(State Pattern)

状态模式:允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。

就像我们平时用的开关,开关有两个状态,开启和关闭,当它处于不同的状态的时候它的行为是不一样的,当我们遇到了更多的状态的时候,就需要动用到了这个状态模式。

Context 上下文环境: 它定义了客户程序需要的接口并维护一个具体状态角色的实例,将与状态相关的操作委托给当前的具体状态对象来处理。
Stata 抽象状态: 定义了一个接口以封装使用上下文环境的一个特定状态相关的行为。
Concrete State 具体状态: 实例抽象状态定义的接口。

下面例子以游戏中坦克为例,坦克架起的时候,攻击力加强,不能移动。收起的时候,可攻击可移动。c++ 实现如下:

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
#include <iostream>
class SiegeTank;
class ISiegeTankState
{
public:
virtual void move(int x, int y) = 0;
virtual void attack() = 0;
};
class SiegeState : public ISiegeTankState
{
public:
SiegeState(SiegeTank* pTank): m_pTank(pTank){}
virtual void move(int x, int y)
{
std::cout << "Can't move in siege mode." << std::endl;
}
virtual void attack()
{
std::cout << "Attacking for 40" << std::endl;
}
private:
SiegeTank* m_pTank;
};
class TankState : public ISiegeTankState
{
public:
TankState(SiegeTank* pTank): m_pTank(pTank){}
virtual void move(int x, int y)
{
std::cout << "Move to (" << x << ", " << y << ")" << std::endl;
}
virtual void attack()
{
std::cout << "Attacking for 20" << std::endl;
}
private:
SiegeTank* m_pTank;
};
class SiegeTank
{
public:
SiegeTank()
{
m_pTankState = new TankState(this);
m_pSiegeState = new SiegeState(this);
m_pSiegeTankState = m_pTankState;
}
void enterTankMode()
{
m_pSiegeTankState = m_pTankState;
std::cout << "Switch to tank mode" << std::endl;
}
void enterSiegeMode()
{
m_pSiegeTankState = m_pSiegeState;
std::cout << "Switch to siege mode" << std::endl;
}
public:
void attack()
{
m_pSiegeTankState->attack();
}
void move(int x, int y)
{
m_pSiegeTankState->move(x, y);
}
private:
void setState(ISiegeTankState* pSiegeTankMode)
{
m_pSiegeTankState = pSiegeTankMode;
}
private:
TankState* m_pTankState;
SiegeState* m_pSiegeState;
ISiegeTankState* m_pSiegeTankState;
};
int main()
{
SiegeTank tank;
tank.enterTankMode();
tank.attack();
tank.move(1, 1);
tank.enterSiegeMode();
tank.attack();
tank.move(2, 2);
tank.enterTankMode();
tank.attack();
tank.move(3, 3);
return 0;
}

状态模式与策略模式
状态模式中,我们将一群行为封装在状态对象中,context 的行为随时可委托到那些状态对象中的一个。当前状态在状态对象的集合中游走改变,反映出context 内部的状态,context的行为也因此跟着改变。
策略模式中,客户通常主动指定Context 所要组合的策略对象是哪一个。

一般来说,策略模式是一个弹性替代继承的一种方案。状态模式则是弹性替代了过多的条件判断。

状态模式主要解决的是党当控制一个状态转换的条件表达式过于复杂的时候,把状态的判断逻辑转移到表示不同状态的一个系列类中,可以吧复杂的判断逻辑简单化。当一个对象行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为时,就可以考虑使用状态模式了。

代理模式(Proxy Pattern)

代理模式: 为另一个对象提供一个替身或占位符以控制对这个对象的访问。使用代理模式创建代表(representative)对象,让代表对象控制某对象的访问,被代理的对象可以是远程的对象,创建开销大的对象或需要安全控制的对象。

代理模式的分类

在C++ 中,智能指针采用引入计数器的方法可以帮助管理内存,而其使用的设计模式就是代理模式,代码重现如下:

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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
#include "stdafx.h"
#include <assert.h>
#define KSAFE_DELETE(p) \
if (p) \
{ \
delete p; \
p = NULL; \
}
class KRefCount
{
public:
KRefCount():m_nCount(0){}
public:
void AddRef(){m_nCount++;}
int Release(){return --m_nCount;}
void Reset(){m_nCount=0;}
private:
int m_nCount;
};
template <typename T>
class KSmartPtr
{
public:
KSmartPtr(void)
: m_pData(NULL)
{
m_pReference = new KRefCount();
m_pReference->AddRef();
}
KSmartPtr(T* pValue)
: m_pData(pValue)
{
m_pReference = new KRefCount();
m_pReference->AddRef();
}
KSmartPtr(const KSmartPtr<T>& sp)
: m_pData(sp.m_pData)
, m_pReference(sp.m_pReference)
{
m_pReference->AddRef();
}
~KSmartPtr(void)
{
if (m_pReference && m_pReference->Release() == 0)
{
KSAFE_DELETE(m_pData);
KSAFE_DELETE(m_pReference);
}
}
inline T& operator*()
{
return *m_pData;
}
inline T* operator->()
{
return m_pData;
}
KSmartPtr<T>& operator=(const KSmartPtr<T>& sp)
{
if (this != &sp)
{
if (m_pReference && m_pReference->Release() == 0)
{
KSAFE_DELETE(m_pData);
KSAFE_DELETE(m_pReference);
}
m_pData = sp.m_pData;
m_pReference = sp.m_pReference;
m_pReference->AddRef();
}
return *this;
}
KSmartPtr<T>& operator=(T* pValue)
{
if (m_pReference && m_pReference->Release() == 0)
{
KSAFE_DELETE(m_pData);
KSAFE_DELETE(m_pReference);
}
m_pData = pValue;
m_pReference = new KRefCount;
m_pReference->AddRef();
return *this;
}
T* Get()
{
T* ptr = NULL;
ptr = m_pData;
return ptr;
}
void Attach(T* pObject)
{
if (m_pReference->Release() == 0)
{
KSAFE_DELETE(m_pData);
KSAFE_DELETE(m_pReference);
}
m_pData = pObject;
m_pReference = new KRefCount;
m_pReference->AddRef();
}
T* Detach()
{
T* ptr = NULL;
if (m_pData)
{
ptr = m_pData;
m_pData = NULL;
m_pReference->Reset();
}
return ptr;
}
private:
KRefCount* m_pReference;
T* m_pData;
};

适配器为适配对象提供了不同的接口,而代理模式则是提供了相同的接口。
装饰者为对象添加多个功能,而代理模式则是控制对对象的访问。
在软件系统中,加一个中间层使我们常用的解决方法,常常就是使用的代理模式。

设计模式之王—MVC(Model - View - Controller)

MVC 模式是复合模式的经典例子,也是现在开发中最常用到的设计模式,下面对它进行一一划分,因为C++ 中的没有现成的例子(或者说我的C++ 水平还很菜,没有意识到~MFC 比较接近),HeadFirst 设计模式中是以java为例写的,iOS开发中也常常用到。

Model: 模型存储数据。对应了 Swift 中的 Album 这个类。
View: 负责模型的可视化展示,并且负责和用户交互,在 Swift 开发中,对应了UIView 这个类。
Controller: 控制器是整个系统的掌控者,它连接了模型层和数据层,并且吧数据在视图中展示出来,监听各种事件,负责数据的各种操作。对应Swift中的 ViewController 类。

下面这个是斯坦福大学公开课中给的MVC 的类图,非常的形象,被各种使用:

view中射出的箭头,指向target,代表在ios中对界面上的操作,反馈给了controller中去。
model 中的小发射塔,设定观察者,当model发生改变,会给观察者们以反馈。典型的例子就是iOS中的键盘呼出。
Model: 它是应用数据和应用的状态,可能是一个数据库,也可能是你发动时创建的内存,或是你从网络得到的东西,它本质上是应用程序的数据,它基本上什么事情都不做。

View:在界面上看到的东西,是互动的对象,它代表着你所使用的界面相称的用户模型,但它不清楚数据本身,视图允许你操纵数据,如果我有一个能改变磁盘上数据的滑动条,这个滑动条就是视图,他不存储任何数据,他们完全是动态的,他们被创建后,使用完就会清楚,他们很容易配置,如表格视图,他不了解数据,只代表数据。

Model:Model是你的应用中数据的存储或数据的表现,相同的模型应该可以在不同的界面中重复使用和未作改变,如果你有代表一些数据集合的应用,假设这是个代表人口和选举信息的数据,这个数据本身,这个模型并不清楚信息是如何展示给用户,因此这个模型能够运用于不同的平台,他能用在不同的应用上,这个的前提是它独立的如何展现

Controller:可以管理并把数据展示给视图,同样的,当视图想要操作数据,控制器会是视图能这样做的管道。在这个基础上,视图和模型永远不应该相互知道或相互交谈,控制器是管理,演示和控制的媒介。控制器基本是告诉视图关于数据的改变以及在视图需要时改变数据,大部分你的应用逻辑都会在控制器中。

那么MVC模式都复合了上边了解过的那些模式呢?

由于我们使用的是上帝视角,所以其实以上部分,谁与谁之间不能对话,不能调用显得没有那么强硬,我们在编码的过程中,实际上可以实现任何操作,但是,应当遵循的原则,是按照图片中所说的,让model 和 View 尽量解耦,所有的操作应当在Controller中完成。

因为目前没有学习 Java ,Swift 也只是简单的上了下手,对MVC 模型的实战留在日后,而MVC 与web的操作在适配MVC 这一模式,使它更符合浏览器 / 服务器的模型,人们叫它 Model 2,以后有时间继续深挖。

只是个开始

至此,Headfrist 设计模式中提到的14种设计模式被我用了大约一周多的时间过了一遍,书讲的很通俗很入门,但缺点就是比较浅,没法对每一种模式有更深入的认识。另外,作为凌驾于所有语言之上的设计模式,认真学习它是必须和刻不容缓的。在此基础上,从这本书开始,我应当继续探索设计模式的内容,以贴近实践的方式体会和理解设计模式给开发带来的优势,下边是对这些设计模式的一个分类和一句话总结。

创建型(4,5未提及)用来处理对象的创建过程:

  1. 单例模式(Singleton Pattern): 确保有且只有一个对象被创建,提供一个访问它的全局访问点。
  2. 工厂方法模式(Factory Method Pattern): 定义一个创建产品对象的工厂接口,而由子类决定要创建的具体类是哪一个,也就是将实际创建工作推迟到子类中去。
  3. 抽象工厂模式(Abstract Factory Pattern): 提供一个创建一些列相关或者相互依赖的接口,而无需指定它们具体的类。
  4. 建造者模式(Builder pattern): 讲一个复杂的构建与其表示分离,使得同样的构建过程可以创建不同的表示。
  5. 原型模式(Prototype Pattern): 用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

结构型用来处理类或对象的组合:

  1. 适配器模式(Adapter Pattern): 讲一个类的接口转换成客户希望的另一个接口,使得原本由于接口不兼容不能一起工作的那些类可以一起工作。
  2. 组合模式(Composite Pattern): 将对象组合成树形结构以表示『部分-整体』的层次结构,使得用户对单个对象和组合对象的使用具有一致性。
  3. 外观模式(Facade Pattern): 为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
  4. 装饰者模式(Decorator Pattern ): 动态的给一个对象添加一些额外的职责,就增加功能来说,这一模式比生成子类更为灵活。(包装一个对象,提供新的行为)。
  5. 代理模式(Proxy Pattern): 为其他对象提供一种代理以控制对这个对象的访问。
  6. 桥接模式(Bridge Pattern): 将抽象部分与实际部分分离,使他们可以独立的变化。
  7. 享元模式(Flyweight Pattern): 以共享的方式高效的支持大量的细粒度的对象。

行为型用来对类和对象怎样交互和分配职责进行描述:

  1. 命令模式(Command Pattern): 将一个请求封装为一个对象,从而使你可用不同的请求对客户端进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。
  2. 迭代器模式(Iterator Pattern): 提供一个方法顺序访问一个聚合对象中的各个元素,而不需要暴露该对象内部的表示。
  3. 观察者模式(Observer Pattern): 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动更新。
  4. 状态模式(State Pattern): 允许一个对象在其内部改变时改变它的行为,使对象看起来似乎修改了它的类。封装基本状态的行为,并使用委托在行为之间切换。
  5. 策略模式: 准备一组算法,并将每个算法封装起来,使用委托来决定使用哪一个。
  6. 模板方法模式(Template Method Pattern): 子类在不改变一个算法的结构情况下,可以重定义该算法的某些特定步骤。
  7. 责任链模式(Chain of Responsibility Pattern): 在该模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链。请求在这个链上传递,直到链上某一个对象决定处理此请求,使得系统可以在不影响客户端的情况下动态的重新组织链和分配责任。
  8. 解释器模式(Interpreter Pattern): 描述了如何为简单的语言顶一个语法,如何在该语言中表示一个句子,以及如何解释这些句子。
  9. 中介者模式(Mediator Pattern) : 定义一个中介对象来封装系列对象之间的交互。中介者使各个对象不需要显示的相互调用,从而使其耦合性松散,而且可以独立的改变他们之间的交互。
  10. 备忘录模式(Memento Pattern): 在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。
  11. 访问者模式(Visitor Pattern): 表示一个作用于某对象结构中的各个元素的操作,它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。

最后,是几个奉劝:

设计模式的魅力是无穷的,而设计模式的学习和体悟,需要通过不断的实践去反思和体会的,旅途刚刚开始,Keep Moving…

script>