设计模式(以C++为例)
工厂模式
工厂模式:简单工厂模式、工厂方法模式、抽象工厂模式
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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46typedef enum
{
T80 = 1,
T99
}TankType;
class Tank
{
public:
virtual void message() = 0;
};
class Tank80:public Tank
{
public:
void message()
{
cout << "Tank80" << endl;
}
};
class Tank99:public Tank
{
public:
void message()
{
cout << "Tank99" << endl;
}
};
class TankFactory
{
public:
Tank* createTank(TankType type)
{
switch(type)
{
case 1:
return new Tank80();
case 2:
return new Tank99();
default:
return NULL;
}
}
};
2. 工厂方法模式
是指定义一个创建对象的接口,让子类决定实例化哪一个类,Factory Method使一个类的实例化延迟到其子类。
主要解决:接口选择的问题。
何时使用:我们明确地计划不同条件下创建不同实例时。
如何解决:让其子类实现工厂接口,返回的也是一个抽象的产品。
关键代码:创建过程在其子类执行。
缺点:每增加一种产品,就需要增加一个对象工厂。相比简单工厂模式,工厂方法模式需要定义更多的类。
1 | class Tank |
3. 抽象工厂模式
提供一个创建一系列相关或相互依赖的对象接口,而无需指定它们的具体类。
主要解决:接口选择的问题。
何时使用:系统的产品有多于一个的产品族,而系统只消费其中某一族的产品。
如何解决:在一个产品族里面,定义多个产品。
关键代码:在一个工厂里聚合多个同类产品。
缺点:产品族扩展非常困难,要增加一个系列的某一产品,既要在抽象的 Creator 里加代码,又要在具体的里面加代码。
1 | class Tank |
策略模式
策略模式是指定义一系列的算法,把它们一个个封装起来,并且使它们可以互相替换。使得算法可以独立于使用它的客户而变化,也就是说这些算法所完成的功能是一样的,对外接口是一样的,只是各自现实上存在差异。
主要解决:在有多种算法相似的情况下,使用 if…else 所带来的复杂和难以维护。
何时使用:一个系统有许多许多类,而区分它们的只是他们直接的行为。
如何解决:将这些算法封装成一个一个的类,任意地替换。
关键代码:实现同一个接口。
缺点: 1、策略类会增多。 2、所有策略类都需要对外暴露。
1 | //传统策略模式实现 |
适配器模式
适配器模式:将一个类的接口转换成客户希望的另一个接口,使得原本由于接口不兼容而不能一起工作的哪些类可以一起工作。
主要解决:主要解决在软件系统中,常常要将一些”现存的对象”放到新的环境中,而新环境要求的接口是现对象不能满足的。
何时使用: 1、系统需要使用现有的类,而此类的接口不符合系统的需要。 2、想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作,这些源类不一定有一致的接口。 3、通过接口转换,将一个类插入另一个类系中。(比如老虎和飞禽,现在多了一个飞虎,在不增加实体的需求下,增加一个适配器,在里面包容一个虎对象,实现飞的接口。)
如何解决:继承或依赖(推荐)。
关键代码:适配器继承或依赖已有的对象,实现想要的目标接口。
缺点:1、过多地使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。
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
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//懒汉式一般实现:非线程安全,getInstance返回的实例指针需要delete
class Singleton
{
public:
static Singleton* getInstance();
~Singleton(){}
private:
static Singleton* m_pSingleton;
Singleton(){}
Singleton(const Singleton& obj) = delete; //明确拒绝
Singleton& operator=(const Singleton& obj) = delete; //明确拒绝
};
Singleton* Singleton::m_pSingleton = NULL;
Singleton* Singleton::getInstance()
{
if(m_pSingleton == NULL)
{
m_pSingleton = new Singleton;
}
return m_pSingleton;
}
//END
//懒汉式:加lock,线程安全
std::mutex mt;
class Singleton
{
public:
static Singleton* getInstance();
private:
Singleton(){}
Singleton(const Singleton&) = delete; //明确拒绝
Singleton& operator=(const Singleton&) = delete; //明确拒绝
static Singleton* m_pSingleton;
};
Singleton* Singleton::m_pSingleton = NULL;
Singleton* Singleton::getInstance()
{
if(m_pSingleton == NULL)
{
mt.lock();
m_pSingleton = new Singleton();
mt.unlock();
}
return m_pSingleton;
}
//END
//返回一个reference指向local static对象
//多线程可能存在不确定性:任何一种non-const static对象,不论它是local或non-local,在多线程环境下“等待某事发生”都会有麻烦。
//解决的方法:在程序的单线程启动阶段手工调用所有reference-returning函数。
class Singleton
{
public:
static Singleton& getInstance();
private:
Singleton(){}
Singleton(const Singleton&) = delete; //明确拒绝
Singleton& operator=(const Singleton&) = delete; //明确拒绝
};
Singleton& Singleton::getInstance()
{
static Singleton singleton;
return singleton;
}
//END
//饿汉式:线程安全,注意delete
class Singleton
{
public:
static Singleton* getInstance();
private:
Singleton(){}
Singleton(const Singleton&) = delete; //明确拒绝
Singleton& operator=(const Singleton&) = delete; //明确拒绝
static Singleton* m_pSingleton;
};
Singleton* Singleton::m_pSingleton = new Singleton();
Singleton* Singleton::getInstance()
{
return m_pSingleton;
}
//END
懒汉式1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23class CSingleton
{
private:
CSingleton()
{
}
static CSingleton *p;
public:
static CSingleton* getInstance()
{
if(p == NULL)
p = new CSingleton();
return p;
}
};
CSingleton* CSingleton::p = NULL;
int main()
{
CSingleton *t = CSingleton::getInstance();
CSingleton *tt = CSingleton::getInstance();
cout << t << endl << tt << endl;
}
饿汉式1
2
3
4
5
6
7
8
9
10
11
12
13
14class CSingleton
{
private:
CSingleton()
{
}
static CSingleton *p;
public:
static CSingleton* getInstance()
{
return p;
}
};
CSingleton* CSingleton::p = new CSingleton();
原型模式
原型模式:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
主要解决:在运行期建立和删除对象。
何时使用:1).当我们的对象类型不是开始就能确定的,而这个类型是在运行期确定的话,那么我们通过这个类型的对象克隆出一个新的对象比较容易一些;2).有的时候,我们需要一个对象在某个状态下的副本,此时,我们使用原型模式是最好的选择;例如:一个对象,经过一段处理之后,其内部的状态发生了变化;这个时候,我们需要一个这个状态的副本,如果直接new一个新的对象的话,但是它的状态是不对的,此时,可以使用原型模式,将原来的对象拷贝一个出来,这个对象就和之前的对象是完全一致的了;3).当我们处理一些比较简单的对象时,并且对象之间的区别很小,可能就几个属性不同而已,那么就可以使用原型模式来完成,省去了创建对象时的麻烦了;4).有的时候,创建对象时,构造函数的参数很多,而自己又不完全的知道每个参数的意义,就可以使用原型模式来创建一个新的对象,不必去理会创建的过程。
适当的时候考虑一下原型模式,能减少对应的工作量,减少程序的复杂度,提高效率。
如何解决:利用已有的一个原型对象,快速地生成和原型对象一样的实例。
关键代码:拷贝,return new className(*this);
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
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
54class Computer
{
public:
void product()
{
installCpu();
installRam();
installGraphicsCard();
}
protected:
virtual void installCpu() = 0;
virtual void installRam() = 0;
virtual void installGraphicsCard() = 0;
};
class ComputerA:public Computer
{
protected:
void installCpu() override
{
cout << "ComputerA install Inter Core i5" << endl;
}
void installRam() override
{
cout << "ComputerA install 2G Ram" << endl;
}
void installGraphicsCard() override
{
cout << "ComputerA install Gtx940 GraphicsCard" << endl;
}
};
class ComputerB:public Computer
{
protected:
void installCpu() override
{
cout << "ComputerB install Inter Core i7" << endl;
}
void installRam() override
{
cout << "ComputerB install 4G Ram" << endl;
}
void installGraphicsCard() override
{
cout << "ComputerB install Gtx960 GraphicsCard" << endl;
}
};
建造者模式
建造者模式:将复杂对象的构建和其表示分离,使得同样的构建过程可以创建不同的表示。
主要解决:一个复杂对象的创建工作,由各个部分的子对象用一定的算法构成;由于需求变化,这个复杂对象的各个部分经常面临变化,但将它们组合在一起的算法却相对稳定。
如何解决:将变与不变分开
关键代码:建造者:创建和提供实例,Director:管理建造出来的实例的依赖关系。
缺点:1、产品必须有共同点,范围有限制。 2、如内部变化复杂,会有很多的建造类。
1 | typedef enum |
外观模式
外观模式:为子系统中的一组接口定义一个一致的界面,外观模式提供了一个高层接口,这个接口使得这一子系统更加容易被使用;对于复杂的系统,系统为客户提供一个简单的接口,把复杂的实现过程封装起来,客户不需要了解系统内部的细节。
主要解决:客户不需要了解系统内部复杂的细节,只需要一个接口;系统入口。
如何解决:客户不直接与系统耦合,而是通过外观类与系统耦合。
关键代码:客户与系统之间加一个外观层,外观层处理系统的调用关系、依赖关系等。
缺点:需要修改时不易继承、不易修改。
1 | class Cpu |
组合模式
组合模式:将对象组合成树形结构以表示“部分-整体”的层次结构,组合模式使得用户对单个对象和组合对象的使用具有一致性。
主要解决:它在我们树型结构的问题中,模糊了简单元素和复杂元素的概念,客户程序可以像处理简单元素一样处理复杂元素,从而使得客户程序与复杂元素的内部结构解耦。
如何解决:树枝和树叶实现统一接口,树枝内部组合该接口。
关键代码:树枝内部组合该接口,并且含有内部属性list,里面放Component。
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
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
class Gril
{
public:
Gril(string name = "gril"):m_string(name){}
string getName()
{
return m_string;
}
private:
string m_string;
};
class Profession
{
public:
virtual ~Profession(){}
virtual void profess() = 0;
};
class YoungMan:public Profession
{
public:
YoungMan(Gril gril):m_gril(gril){}
void profess()
{
cout << "Young man love " << m_gril.getName().data() << endl;
}
private:
Gril m_gril;
};
class ManProxy:public Profession
{
public:
ManProxy(Gril gril):m_man(new YoungMan(gril)){}
void profess()
{
cout << "I am Proxy" << endl;
m_man->profess();
}
private:
YoungMan* m_man;
};
int main(int argc, char *argv[])
{
Gril gril("hei"); //代理
Profession* proxy = new ManProxy(gril);
proxy->profess();
delete proxy;
return 0;
}
享元模式
享元模式:运用共享技术有效地支持大量细粒度的对象。
主要解决:在有大量对象时,把其中共同的部分抽象出来,如果有相同的业务请求,直接返回内存中已有的对象,避免重新创建。
如何解决:用唯一标识码判断,如果内存中有,则返回这个唯一标识码所标识的对象。
关键代码:将内部状态作为标识,进行共享。
1 | //以Money的类别作为内部标识,面值作为外部状态。 |
桥接模式
桥接模式:将抽象部分与实现部分分离,使它们都可以独立变换。
主要解决:在有很多中可能会变化的情况下,用继承会造成类爆炸问题,不易扩展。
如何解决:把不同的分类分离出来,使它们独立变化,减少它们之间的耦合。
关键代码:将现实独立出来,抽象类依赖现实类。
1 | //将各种App、各种手机全部独立分开,使其自由组合桥接 |
装饰模式
装饰模式:动态地给一个对象添加一些额外的功能,就新增加功能来说,装饰器模式比生产子类更加灵活。
主要解决:通常我们为了扩展一个类经常使用继承的方式,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀。
如何解决:将具体的功能划分,同时继承装饰者类。
关键代码:装饰类复合和继承组件类,具体的扩展类重写父类的方法。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
97class Dumplings //抽象类 饺子
{
public:
virtual ~Dumplings(){}
virtual void showDressing() = 0;
};
class MeatDumplings:public Dumplings //现实类 肉馅饺子
{
public:
~MeatDumplings(){ cout << "~MeatDumplings()" << endl; }
void showDressing()
{
cout << "Add Meat" << endl;
}
};
class DecoratorDumpling:public Dumplings //装饰类
{
public:
DecoratorDumpling(Dumplings* d):m_dumpling(d){}
virtual ~DecoratorDumpling(){ cout << "~DecoratorDumpling()" << endl; }
void showDressing()
{
m_dumpling->showDressing();
}
private:
Dumplings* m_dumpling;
};
class SaltDecorator:public DecoratorDumpling // 装饰类 加盐
{
public:
SaltDecorator(Dumplings* d):DecoratorDumpling(d){}
~SaltDecorator(){ cout << "~SaltDecorator()" << endl; }
void showDressing()
{
DecoratorDumpling::showDressing(); //注意点
addDressing();
}
private:
void addDressing()
{
cout << "Add Salt" << endl;
}
};
class OilDecorator:public DecoratorDumpling //装饰类 加油
{
public:
OilDecorator(Dumplings* d):DecoratorDumpling(d){}
~OilDecorator(){ cout << "~OilDecorator()" << endl; }
void showDressing()
{
DecoratorDumpling::showDressing(); //注意点
addDressing();
}
private:
void addDressing()
{
cout << "Add Oil" << endl;
}
};
class CabbageDecorator:public DecoratorDumpling //装饰类 加蔬菜
{
public:
CabbageDecorator(Dumplings* d):DecoratorDumpling(d){}
~CabbageDecorator(){ cout << "~CabbageDecorator()" << endl; }
void showDressing()
{
DecoratorDumpling::showDressing(); //注意点
addDressing();
}
private:
void addDressing()
{
cout << "Add Cabbage" << endl;
}
};
int main()
{
Dumplings* d = new MeatDumplings; //原始的肉饺子
Dumplings* d1 = new SaltDecorator(d); //加盐后的饺子
Dumplings* d2 = new OilDecorator(d1); //加油后的饺子
Dumplings* d3 = new CabbageDecorator(d2); //加蔬菜后的饺子
d3->showDressing();
delete d;
delete d1;
delete d2;
delete d3;
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
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
89typedef struct //需要保存的信息
{
int grade;
string arm;
string corps;
}GameValue;
class Memento //备忘录类
{
public:
Memento(){}
Memento(GameValue value):m_gameValue(value){}
GameValue getValue()
{
return m_gameValue;
}
private:
GameValue m_gameValue;
};
class Game //客户类 游戏
{
public:
Game(GameValue value):m_gameValue(value)
{}
void addGrade() //等级增加
{
m_gameValue.grade++;
}
void replaceArm(string arm) //更换武器
{
m_gameValue.arm = arm;
}
void replaceCorps(string corps) //更换工会
{
m_gameValue.corps = corps;
}
Memento saveValue() //保存当前信息
{
Memento memento(m_gameValue);
return memento;
}
void load(Memento memento) //载入信息
{
m_gameValue = memento.getValue();
}
void showValue()
{
cout << "Grade: " << m_gameValue.grade << endl;
cout << "Arm : " << m_gameValue.arm.data() << endl;
cout << "Corps: " << m_gameValue.corps.data() << endl;
}
private:
GameValue m_gameValue;
};
class Caretake //备忘录管理类
{
public:
void save(Memento memento) //保存信息
{
m_memento = memento;
}
Memento load() //读已保存的信息
{
return m_memento;
}
private:
Memento m_memento;
};
int main()
{
GameValue v1 = {0, "Ak", "3K"};
Game game(v1); //初始值
game.addGrade();
game.showValue();
cout << "----------" << endl;
Caretake care;
care.save(game.saveValue()); //保存当前值
game.addGrade(); //修改当前值
game.replaceArm("M16");
game.replaceCorps("123");
game.showValue();
cout << "----------" << endl;
game.load(care.load()); //恢复初始值
game.showValue();
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
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
92class Mediator;
class Person //抽象同事类
{
public:
virtual ~Person(){}
virtual void setMediator(Mediator* mediator)
{
m_mediator = mediator;
}
virtual void sendMessage(const string& message) = 0;
virtual void getMessage(const string& message) = 0;
protected:
Mediator* m_mediator;
};
class Mediator //抽象中介类
{
public:
virtual ~Mediator(){}
virtual void setBuyer(Person* buyer) = 0;
virtual void setSeller(Person* seller) = 0;
virtual void send(const string& message, Person* person) = 0;
};
class Buyer:public Person //买家类
{
public:
void sendMessage(const string& message)
{
m_mediator->send(message, this);
}
void getMessage(const string& message)
{
cout << "Buyer Get: " << message.data() << endl;
}
};
class Seller:public Person //卖家类
{
public:
void sendMessage(const string& message)
{
m_mediator->send(message, this);
}
void getMessage(const string& message)
{
cout << "Seller Get: " << message.data() << endl;
}
};
class HouseMediator:public Mediator //具体的中介类
{
public:
HouseMediator():m_buyer(nullptr),m_seller(nullptr){}
void setBuyer(Person* buyer)
{
m_buyer = buyer;
}
void setSeller(Person *seller)
{
m_seller = seller;
}
void send(const string& message, Person* person)
{
if(person == m_buyer)
{
m_seller->getMessage(message);
}
if(person == m_seller)
{
m_buyer->getMessage(message);
}
}
private:
Person* m_buyer;
Person* m_seller;
};
int main()
{
Person* buyer = new Buyer;
Person* seller = new Seller;
Mediator* houseMediator = new HouseMediator;
buyer->setMediator(houseMediator);
seller->setMediator(houseMediator);
houseMediator->setBuyer(buyer);
houseMediator->setSeller(seller);
buyer->sendMessage("1.5?");
seller->sendMessage("2!!!");
return 0;
}
职责链模式
职责链模式:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之前的耦合关系,将这些对象连成一条链,并沿着这条链传递请求,直到有一个对象处理它为止。
主要解决:职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无需关心请求的处理细节和请求的传递,所有职责链将请求的发送者和请求的处理者解耦了。
如何解决:职责链链扣类都现实统一的接口。
关键代码:Handler内指明其上级,handleRequest()里判断是否合适,不合适则传递给上级。
1 | enum RequestLevel |
观察者模式
观察者模式:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都要得到通知并自动更新。
主要解决:一个对象更新,其它对象也要更新。
如何解决:目标类通知函数通知所有观察者自动更新。
关键代码:在目标类中增加一个ArrayList来存放观察者们。
1 | //数据模型为目标类,视图为观察者类。当数据模型发生改变时,通知视图类更新 |