结构型模式

结构型模式7个

适配器模式(Adapter)、桥接模式(Bridge)、装饰模式(Decorator)、组合模式(Composite)、外观模式(Facade)、享元模式(Flyweight)、代理模式(Proxy)

装饰者模式( Pattern)

装饰者模式( Pattern)属于结构型模式,动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。它是通过创建一个包装对象,通过包装对象来包裹真实的对象,以达到装饰目的。

装饰者模式

  • Component:抽象组件,给具体类对象动态地添加职责。
  • ConcreteComponent:抽象组件的具体实现类。
  • Decorator:抽象装饰者,继承Component,用于拓展Component类的功能,但对于Component来说无需知道Decorator的存在。
  • ConcreteDecorator:装饰者具体实现类。

咖啡添加了不同的调料,将收取不同的费用。因为调料数量众多,我们不能为每一种口味的咖啡创建一个类。

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
// 咖啡基类Coffee(被装饰者的基类):
class Coffee {
protected:
string description;
public:
virtual double cost() = 0;
virtual string getDescription() {
return description;
}
};
// 以摩卡咖啡为例,实现具体咖啡类Mocha,最开始有自己的简单装饰:
class Mocha :public Coffee {
public:
Mocha() {
description="摩卡";
}
//重写,返回价格
virtual double cost() {
return 10;
}
};
// 抽象调料基类(CondimentDecorator,装饰者基类)用来对原味咖啡进行多层装饰,每层装饰增加一些配料。
class CondimentDecorator :public Coffee {
};
// 装饰者奶泡类(Whip,装饰者的第一层):
class Whip :public CondimentDecorator {
Coffee* coffee;
public:
Whip(Coffee* coffee) {
this->coffee = coffee;
}
//价格增加0.5元
double cost() {
return 0.5 + coffee->cost();
}
//增加第一层装饰
string getDescription() {
return coffee->getDescription() + "+奶泡";
}
};
// 装饰者牛奶类(Milk,装饰者的第二层):
class Milk :public CondimentDecorator {
Coffee* coffee;
public:
Milk(Coffee* coffee) {
this->coffee = coffee;
}
//价格增加1.7元
double cost() {
return 1.7 + coffee->cost();
}
//增加第二层装饰
string getDescription() {
return coffee->getDescription() + "+牛奶";
}
};
#include <iostream>
#include <string>
using namespace std;
int main(){
Coffee* mocha=new Mocha;
cout << mocha->getDescription() << " 价格:" << mocha->cost()<<endl;
//添加奶泡
Coffee* mocha1 = new Whip(mocha);
cout << mocha1->getDescription() << " 价格:" << mocha1->cost() << endl;
//添加牛奶
mocha1 = new Milk(mocha);
cout << mocha1->getDescription() << " 价格:" << mocha1->cost() << endl;
//添加奶泡和牛奶
mocha1 = new Whip(mocha);
mocha1 = new Milk(mocha1);
cout << mocha1->getDescription() << " 价格:" << mocha1->cost() << endl;
system("pause");
}

使用场景:

  • (1)在不必改变原类和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。
  • (2)当不能采用继承和组合的方式对系统进行扩充或者不利于系统扩展和维护时。

外观模式(Facade Pattern)

外观模式(Facade Pattern)属于结构型模式,提供了一个统一的接口(具体类),用来访问子系统的一群接口(具体类)。外观定义了一个高层接口,让子系统更容易使用。
装饰者模式

  • Client:客户类,通过外观类与子系统进行交互。
  • Facade:外观类,知道哪些子系统类负责处理请求,将客户端的请求代理给适当的子系统对象。
  • Subsystem:子系统类,实现子系统功能,处理外观类指派的任务,注意子系统类不含有外观类的引用。
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
// 把《倚天屠龙记》张无忌当作一个系统,他作为一个武侠,本身分为三个子系统,分别是招式、内功和经脉。
// 三个子系统分别是招式、内功和经脉
//子系统招式
class ZhaoShi {
public:
void taiJiQuan() {
cout << "使用着招式太极拳" << endl;
}
void qiShangQuan() {
cout << "使用招式七伤拳"<< endl;
}
void shengHuoLing() {
cout << "使用招式圣火令"<< endl;
}
};
//子系统内功
class NeiGong {
public:
void jiuYang(){
cout << "使用九阳神功"<< endl;
}
void qianKun() {
cout << "使用乾坤大挪移"<< endl;
}
};
//子系统经脉
class JingMai {
public:
void jingMai() {
cout << "开启经脉"<< endl;
}
};
//外观类张无忌
class ZhangWuJi {
private:
JingMai jingMai;
ZhaoShi zhaoShi;
NeiGong neiGong;
public:
ZhangWuJi(){}
//使用乾坤大挪移
void qianKun() {
jingMai.jingMai();//开启经脉
neiGong.qianKun();//使用内功乾坤大挪移
}
//使用七伤拳
void qiShang() {
jingMai.jingMai(); //开启经脉
neiGong.jiuYang(); //使用内功九阳神功
zhaoShi.qiShangQuan();//使用招式七伤拳
}
};

应用场景

  • (1)客户端需要与多个子系统交互时,可使用外观模式。如当维护一个遗留的大型系统时,可能这个系统难以维护和拓展,但因为含有重要功能,新的需求必须依赖于它,则可以使用外观类,来为这个粗糙复杂的遗留代码提供一个简单的类接口,让新系统和外观类交互,而外观类负责与遗留的代码进行交互。
  • (2)客户端程序与多个子系统之间存在很大的依赖性。引入外观类将子系统与客户以及其他子系统解耦,可以提高子系统的独立性和可移植性。

适配器模式(Adapter Pattern)

适配器模式(Adapter Pattern)属结构性模式,将一个类的接口转换成客户期望的另一个接口。适配器让原本接口不兼容的类可以合作无间。

适配器模式

  • Client:客户类。
  • Target:目标抽象类;
  • Adapter:适配器类;
  • Adaptee:适配者类;
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
// 用火鸡冒充鸭子的例子来讲解。
//抽象鸭类
class Duck {
public:
virtual void quack() = 0;
virtual void fly() = 0;
};
//绿头鸭
class MallardDuck {
public:
virtual void quack() {
cout << "绿头鸭呱呱叫"<< endl;
}
virtual void fly() {
cout << "绿头鸭开始飞"<< endl;
}
};
// 拥有火鸡类,火鸡同样可以叫和飞,但是叫声是咯咯叫,飞行也没有鸭子飞的远。
class Turkey {
public:
void gobble() {
cout << "火鸡咯咯叫" << endl;
}
void fly() {
cout << "火鸡短距离飞" << endl;
}
};
// 可以使用适配器可以来包装火鸡对象和函数接口。
class TurkeyAdapter:public Duck {
Turkey turkey;
public:
TurkeyAdapter(const Turkey & turkey) : turkey(turkey){}
virtual void quack() {
turkey.gobble();
}
//火鸡飞行5次才能达到鸭子的飞行距离
virtual void fly() {
for(int i=0;i < 5;++i)
turkey.fly();
}
};

应用场景

  • (1)系统需要使用现有的类,而这些类的接口不符合系统的需要。
  • (2)想要建立一个可以重复使用的类(适配器),用于与一些彼此之间没有太大关联的类建立联系。

适配器模式分为对象适配器和类适配器。前面所概述的是对象适配器。类适配器使用有限,因为类适配器需要使用多重继承,这个在java、C#中没有办法实现,但在C++中可以实现。
类适配器
适配器模式(对象适配器)将一个对象包装起来改变其函数接口,来适应现有系统的需要; 装饰者模式讲一个对象包装起来以增加新的行为和责任;而外观模式将一群对象包装起来,以简化其函数接口。

组合模式

将对象组合成树形结构以表示“部分-整体”的层次结构。组合使得用户对单个对象和组合对象的使用具有一致性。
组合模式
FinanceDepartment、HRDepartment两个类作为叶结点,因此没有定义添加函数。而ConcreteCompany类可以作为中间结点,所以可以有添加函数。那么怎么添加呢?这个类中定义了一个链表,用来放添加的元素。

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
class Company
{
public:
Company(string name) { m_name = name; }
virtual ~Company(){}
virtual void Add(Company *pCom){}
virtual void Show(int depth) {}
protected:
string m_name;
};
//具体公司
class ConcreteCompany : public Company
{
public:
ConcreteCompany(string name): Company(name) {}
virtual ~ConcreteCompany() {}
void Add(Company *pCom) { m_listCompany.push_back(pCom); } //位于树的中间,可以增加子树
void Show(int depth)
{
for(int i = 0;i < depth; i++)
cout<<"-";
cout << m_name << endl;
list<Company *>::iterator iter=m_listCompany.begin();
for(; iter != m_listCompany.end(); iter++) //显示下层结点
(*iter)->Show(depth + 2);
}
private:
list<Company *> m_listCompany;
};
//具体的部门,财务部
class FinanceDepartment : public Company
{
public:
FinanceDepartment(string name):Company(name){}
virtual ~FinanceDepartment() {}
virtual void Show(int depth) //只需显示,无限添加函数,因为已是叶结点
{
for(int i = 0; i < depth; i++)
cout<<"-";
cout<< m_name << endl;
}
};
//具体的部门,人力资源部
class HRDepartment :public Company
{
public:
HRDepartment(string name):Company(name){}
virtual ~HRDepartment() {}
virtual void Show(int depth) //只需显示,无限添加函数,因为已是叶结点
{
for(int i = 0; i < depth; i++)
cout<<"-";
cout<< m_name << endl;
}
};

代理模式

为其他对象提供一种代理以控制对这个对象的访问。有四种常用的情况:(1)远程代理,(2)虚代理,(3)保护代理,(4)智能引用。本文主要介绍虚代理和智能引用两种情况。
代理模式
考虑一个可以在文档中嵌入图形对象的文档编辑器。有些图形对象的创建开销很大。但是打开文档必须很迅速,因此我们在打开文档时应避免一次性创建所有开销很大的对象。这里就可以运用代理模式,在打开文档时,并不打开图形对象,而是打开图形对象的代理以替代真实的图形。待到真正需要打开图形时,仍由代理负责打开。

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
class Image
{
public:
Image(string name): m_imageName(name) {}
virtual ~Image() {}
virtual void Show() {}
protected:
string m_imageName;
};
class BigImage: public Image
{
public:
BigImage(string name):Image(name) {}
~BigImage() {}
void Show() { cout<<"Show big image : "<<m_imageName<<endl; }
};
class BigImageProxy: public Image
{
private:
BigImage *m_bigImage;
public:
BigImageProxy(string name):Image(name),m_bigImage(0) {}
~BigImageProxy() { delete m_bigImage; }
void Show()
{
if(m_bigImage == NULL)
m_bigImage = new BigImage(m_imageName);
m_bigImage->Show();
}
};
int main()
{
Image *image = new BigImageProxy("proxy.jpg"); //代理
image->Show(); //需要时由代理负责打开
delete image;
return 0;
}

享元模式

享元模式的主要目的是实现对象的共享,即共享池,当系统中对象多的时候可以减少内存的开销,通常与工厂模式一起使用。
享元模式
FlyWeightFactory负责创建和管理享元单元,当一个客户端请求时,工厂需要检查当前对象池中是否有符合条件的对象,如果有,就返回已经存在的对象,如果没有,则创建一个新对象,FlyWeight是超类。

在围棋中,棋子就是大量细粒度的对象。其属性有内在的,比如颜色、形状等,也有外在的,比如在棋盘上的位置。内在的属性是可以共享的,区分在于外在属性。因此,可以这样设计,只需定义两个棋子的对象,一颗黑棋和一颗白棋,这两个对象含棋子的内在属性;棋子的外在属性,即在棋盘上的位置可以提取出来,存放在单独的容器中。相比之前的方案,现在容器中仅仅存放了位置属性,而原来则是棋子对象。显然,现在的方案大大减少了对于空间的需求。

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
//棋子颜色
enum PieceColor {BLACK, WHITE};
//棋子位置
struct PiecePos
{
int x;
int y;
PiecePos(int a, int b): x(a), y(b) {}
};
//棋子定义
class Piece
{
protected:
PieceColor m_color; //颜色
public:
Piece(PieceColor color): m_color(color) {}
~Piece() {}
virtual void Draw() {}
};
class BlackPiece: public Piece
{
public:
BlackPiece(PieceColor color): Piece(color) {}
~BlackPiece() {}
void Draw() { cout<<"绘制一颗黑棋\n"; }
};
class WhitePiece: public Piece
{
public:
WhitePiece(PieceColor color): Piece(color) {}
~WhitePiece() {}
void Draw() { cout<<"绘制一颗白棋\n";}
};
// 相应棋盘的定义为:
class PieceBoard
{
private:
vector<PiecePos> m_vecPos; //存放棋子的位置
Piece *m_blackPiece; //黑棋棋子
Piece *m_whitePiece; //白棋棋子
string m_blackName;
string m_whiteName;
public:
PieceBoard(string black, string white): m_blackName(black), m_whiteName(white)
{
m_blackPiece = NULL;
m_whitePiece = NULL;
}
~PieceBoard() { delete m_blackPiece; delete m_whitePiece;}
void SetPiece(PieceColor color, PiecePos pos)
{
if(color == BLACK)
{
if(m_blackPiece == NULL) //只有一颗黑棋
m_blackPiece = new BlackPiece(color);
cout<<m_blackName<<"在位置("<<pos.x<<','<<pos.y<<")";
m_blackPiece->Draw();
}
else
{
if(m_whitePiece == NULL)
m_whitePiece = new WhitePiece(color);
cout<<m_whiteName<<"在位置("<<pos.x<<','<<pos.y<<")";
m_whitePiece->Draw();
}
m_vecPos.push_back(pos);
}
};

桥接模式

桥接模式就是把事物和其具体实现分开,使他们可以各自独立的变化。桥接的用意是:将抽象化与实现化解耦,使得二者可以独立变化。
桥接模式
考虑装操作系统,有多种配置的计算机,同样也有多款操作系统。如何运用桥接模式呢?可以将操作系统和计算机分别抽象出来,让它们各自发展,减少它们的耦合度。当然了,两者之间有标准的接口。这样设计,不论是对于计算机,还是操作系统都是非常有利的。

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
//操作系统
class OS
{
public:
virtual void InstallOS_Imp() {}
};
class WindowOS: public OS
{
public:
void InstallOS_Imp() { cout<<"安装Window操作系统"<< endl; }
};
class LinuxOS: public OS
{
public:
void InstallOS_Imp() { cout<<"安装Linux操作系统"<< endl; }
};
//计算机
class Computer
{
public:
virtual void InstallOS(OS *os) {}
};
class DellComputer: public Computer
{
public:
void InstallOS(OS *os) { os->InstallOS_Imp(); }
};
class AppleComputer: public Computer
{
public:
void InstallOS(OS *os) { os->InstallOS_Imp(); }
};