设计模式——结构型模式

书接上文“设计模式——创建型模式”,上回说到前人创造出了很多创建型的模式,这回我们说说结构型模式。先以教科书形式介绍一下。

结构型模式(Structural Pattern)描述如何将类或者对 象结合在一起形成更大的结构,就像搭积木,可以通过 简单积木的组合形成复杂的、功能更为强大的结构。

  • 类结构型模式
    类结构型模式关心类的组合,由多个类可以组合成一个更大的系统,在类结构型模式中一般只存在继承关系和实现关系。
  • 对象结构型模式
    类与对象的组合,通过关联关系使得在一 个类中定义另一个类的实例对象,然后通过该对象调用其方法。

根据“合成复用原则”,在系统中尽量使用关联关系来替代继 承关系,因此大部分结构型模式都是对象结构型模式。

能看得懂,但是不够生动。接下来我们让它生动、活泼一下。

在元祖王朝赛博坦帝国的一个边远矿业基地,矿区提纯运输车形态的矿工钢锁被工作调动到了这个位于某个卫星上的矿厂。本来与世无争的钢锁,遇到了一起恐怖事件,由此改变了他的火种(心灵)变形形态(肉体)人生轨迹(命运)。他就是我们今天的主角变形金刚。变形金刚分为两派,一为博派(Autobots),一为狂派(Decepticons),二者都有变形的能力。

让我们抽象一下,是否可以将Transforms的变形能力抽化出来,然后再让博派和狂派分别去实现自己的变形方法。

Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Transformer {
public:
virtual void transform() = 0;
};

class Autobot {
public:
void AutobotsTransform() {
printf("Autobots Transform, Please...\n");
}
};

class TransformerAdapter : public Transformer {
private:
Autobot *m_a;
public:
TransformerAdapter(Autobot *a) {
m_a = a;
}
void transform();
};

这就是“适配器模式”


适配器模式

再教科书一下。。。

adapter_1

adapter_2


刚刚收到一个需求,要为每一个变形金刚做一个自己独有的变形特效,而且在变形的过程中要加入背景音乐。所以我们需要修改一下之前的“适配器”设计模式,增加一层背景效果层,然后将背景效果传递给变形金刚,当变形金刚变形的时候将效果播放出来就好了。

Example:

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
class Background {
public:
virtual void Display() = 0;
};

class TransformBackground : public Background {
public:
void Display() {
printf("ki ka ka ka......\n");
}
};

class Transformer {
protected:
Background *bg;
public:
virtual void transform() = 0;
};

class Decepticon : public Transformer {
public:
Decepticon(Background* b) {
bg = b;
}
void transform() {
bg->Display();
printf("Decepticon Transform, Please...\n");
}
};

这就是“桥模式”


桥模式

再教科书一下。。。

bridge_1

优点:

  • 分离抽象接口及其实现部分。
  • 桥接模式提高了系统的可扩充性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统。
  • 实现细节对客户透明,可以对用户隐藏实现细节。

bridge_2

缺点:

  • 桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程
  • 桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围具有一定的局限性。

又来需求了,现在我们需要变形金刚开口说话,以后还需要变形金刚会开炮。总结一下,变形是一个基本的功能,然后先增加一个说话的功能,如果有需要以后还可以增加开炮的功能。每增加一个功能不修改之前的代码,因为修改已经测试过的代码会给程序的稳定性带来隐患。

Example:

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
class Transformer {
protected:
Background *bg;
public:
virtual void transform() = 0;
};

class Bumblebee : public Transformer {
public:
void transform() {
printf("Bumblebee Transform, Please...\n");
}
};

class TransformerDecorator : public Transformer {
protected:
Transformer* tf;
public:
virtual void transform() {
tf->transform();
}
};

class BumblebeeSay : public TransformerDecorator {
public:
BumblebeeSay(Transformer* t) {
this->tf = t;
}
void say() {
printf("wuwuwuwu...\n");
}
void transform() {
TransformerDecorator::transform();
say();
}
};

这就是“装饰模式”


装饰模式

再教科书一下。。。

装饰模式(Decorator Pattern) :动态地给一个对象增加一些额外的职责(Responsibility),就增加对象功能来说,装饰模式比生成子类实现更为灵活。

decorator_1

  • 装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性。
  • 可以通过一种动态的方式来扩展一个对象的功能,通过配置文件可以在运行时选择不同的装饰器,从而实现不同的行为。
  • 通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合。可以使用多个具体装饰类来装饰同一对象,得到功能更为强大的对象。
  • 具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类,在使用时再对其进行组合,原有代码无须改变,符合“开闭原则”

decorator_2

  • 这种比继承更加灵活机动的特性,也同时意味着装饰模式比继承更加易于出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为烦琐。

战争一触即发,为了取得胜利,我们需要快速的生产变形金刚,需要弄一个map,存储已经创建好的,当有战事发生时,从map里直接取出进行战斗。

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
class Transformer {
protected:
Background *bg;
public:
virtual void transform() = 0;
};

class Bumblebee : public Transformer {
public:
void transform() {
printf("Bumblebee Transform, Please...\n");
}
};

class TransformerFlyweight {
private:
std::map<std::string,Transformer*> members;
public:
Transformer* get(std::string key){
Transformer* tf = nullptr;
auto search = members.find(key);
if(search != members.end()) {
tf = search->second;
} else {
// new
tf = new Bumblebee();
members.insert(std::make_pair(key, tf));
}
return tf;
}
};

这就是“享元模式”


享元模式

再教科书一下。。。

享元模式是一个考虑系统性能的设计模式,通过使用享元模式可以节约内存空间,提高系统的性能。运用共享技术有效地支持大量细粒度对象的复用。系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用。由于享元模式要求能够共享的对象必须是细粒度对象,因此它又称为轻量级模式,它是一种对象结构型模式。

flyweight_1

优点

  • 享元模式的优点在于它可以极大减少内存中对象的数量,使得相同对象或相似对象在内存中只保存一份。
  • 享元模式的外部状态相对独立,而且不会影响其内部状态,从而使得享元对象可以在不同的环境中被共享。

flyweight_2

缺点

  • 享元模式使得系统更加复杂,需要分离出内部状态和外部状态,这使得程序的逻辑复杂化。

变形金刚出了个Bug,若在每次变形过程中收到攻击,变形金刚将毫无防御能力。所以需要增加一个防护罩,在变形前开启,在变形后关闭,这样增加变形金刚的变形过程中的防御能力。

Example:

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
class Transformer {
protected:
Background *bg;
public:
virtual void transform() = 0;
};

class Bumblebee : public Transformer {
public:
void transform() {
printf("Bumblebee Transform, Please...\n");
}
};

class TransformerProxy : public Transformer {
private:
Transformer *m_tf;
void save_on() {
printf("saving...\n");
}
void save_off() {
printf("save done\n");
}
public:
TransformerProxy(Transformer *tf) {
m_tf = tf;
}
void transform() {
save_on();
m_tf->transform();
save_off();
}
};

这就是“代理模式”


代理模式

proxy_1

优点

  • 代理模式能够协调调用者和被调用者,在一定程度上降低了系 统的耦合度。
  • 远程代理使得客户端可以访问在远程机器上的对象,远程机器 可能具有更好的计算性能与处理速度,可以快速响应并处理客户端请求。
  • 虚拟代理通过使用一个小对象来代表一个大对象,可以减少系 统资源的消耗,对系统进行优化并提高运行速度。
  • 保护代理可以控制对真实对象的使用权限。

proxy_2

缺点

  • 由于在客户端和真实主题之间增加了代理对象,因此 有些类型的代理模式可能会造成请求的处理速度变慢。
  • 实现代理模式需要额外的工作,有些代理模式的实现 非常复杂。

参考&鸣谢