0%

软件设计模式 - 结构型模式

软件设计模式 - 结构型模式

结构型模式介绍类和对象的组装方式,同时保持较大的灵活性和高效性。

结构型模式包括:适配器,桥接,组合,装饰,外观,享元,代理。

适配器模式(Adapter)

适配器模式是为了解决不同接口之间的适配问题。

适配器通常分为两类:对象适配器,类适配器。

对象适配器

对象适配器是通过实现接口来做到适配的,如下:

Object Adapter

示例代码:

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
class DrawService {
// 绘图服务
public:
void paint(Graphics&) {
// 使用Graphics绘图
}
Graphics& getCanvasGraphics(canvas&) {
// 图形绘板转换
}
};

class DrawInterface {
// 绘图适配器接口
protected:
void draw(canvas&) = 0;
};

class DrawAdapter : public DrawInterface {
// 绘图适配器
protected:
void draw(canvas& c) { // 接口适配
auto g = service.getCanvasGraphics(c);
service.paint(g);
}
private:
DrawService service;
};

类适配器

类适配器是通过面相对象编程的继承达到的,用于多继承方式实现。

如图所示:

Class Adapter

实例代码:

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
class DrawGraphics {
// 图案绘制
public:
void draw(Graphics&) {
// 绘制函数
}
};

class DrawService {
// 绘制服务
protected:
void paint(Graphics&) {
// 绘制
}
Graphics& getCanvasGraphics(canvas&) {
// 转换画板
}
};

class DrawAdapter : public DrawGraphics, public DrawService {
// 绘制适配器
public:
void draw(canvas& c) {
auto g = getCanvasGraphics(c);
paint(g);
}
};

桥接模式(Bridge)

桥接模式是将抽象和实现分为两个独立的层次,将原本不同维度的继承方式改成组合方式。

如下图所示:

Bridge

示例代码:

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
// 画笔风格

class Style {
public:
void draw(Canvas&) = 0;
};

class Pencil : public Style {
public:
void draw(Canvas&) {
// TODO: 铅笔绘制实现
}
};

class Brush : public Style {
public:
void draw(Canvas&) {
// TODO: 画刷绘制实现
}
};

class Pastel : public Style {
public:
void draw(Canvas&) {
// TODO: 蜡笔绘制实现
}
};

// 形状
class Square {
public:
void drawWithPen(Style& pen) {
pen.draw(this->getCanvas());
}
};

class Circle {
public:
void drawWithPen(Style& pen) {
pen.draw(this->getCanvas());
}
};

形状类只是抽象绘制层,可以绘制不同风格的图形,但是对于具体的绘制实现其实由不同风格的画笔来实现。

组合模式(Composite)

组合模式用于将对象组合成树状结构,通常这种模式的实现方式多见为“盒子”(Box)与“产品”(Item)的交互方式,盒子本身也是产品,盒子可以存放产品,从而产生递归嵌套的结构。

结构如下:

Composite

示例代码:

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
class Shape {
public:
void draw() = 0;
protected:
float x, y;
};

class Circle : public Shape {
public:
void draw() {
// 绘制圆形
}
private:
float radius;
};

class Square : public Shape {
public:
void draw() {
// 绘制方块
}
private:
float width, height;
};

// 组合图形
class CompoundShape : public Shape {
public:
void draw() {
// 迭代绘制所有子图形
}
private:
std::vector<Shape> childs;
};

装饰模式(Decorator)

装饰器模式允许将对象放入包含某种行为的特殊对象,从而为原来的对象绑定特殊对象的新行为。

结构如下:

Decorator
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 Shape {
public:
virtual draw() = 0;
};

class Circle : public {
public:
void draw() {
// TODO: 绘制
}
private:
float radius;
};

// 装饰器
class BrushDecorator : public Shape {
public:
BrushDecorator(Shape& s) {
// TODO: 设置画刷风格
}
void draw() {
// 绘制
Shape::draw();
}
};

class PastelDecorator : public Shape {
public:
PastelDecorator(Shape& s) {
// TODO: 设置蜡笔风格
}
void draw() {
// 绘制
Shape::draw();
}
};

使用装饰器模式,如下:

1
2
3
4
5
6
7
8
9
Circle circle;

auto circleWithBrush = new BrushDecorator(circle);
auto circleWithPastel = new PastelDecorator(circle);
auto circleWithBrushPastel = new PastelDecorator(circleWithBrush); // 嵌套装饰

circleWithBrush.draw();
circleWithPastel.draw();
circleWithBrushPastel.draw();

外观模式(Facade)

外观模式是为了给库/框架设计一套简单的接口,这样对外减少细节暴露,从而减少系统的耦合度。

如下示例,设计了一种统一风格的徽章绘制模式:

Facade

示例代码:

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
class Shape {
public:
void draw(Canvas&) = 0;
};

class CompoundShape : public Shape {
public:
void addChild(Shape& c) {
// 添加图形
}
void draw(Canvas&) {
// 绘制图形
}
private:
std::vector<Shape> childs;
};

class Brush {
public:
void setPen(Canvas&);
};

class Background {
public:
void fill(Canvas&);
};

class Badget {
public:
void drawBadget(Shape& s) {
// 绘制徽章
auto canvas = this->getCanvas();
assert(canvas != nullptr);
br.setPen(*canvas);
bg.fill(*canvas);
s.draw(*canvas);
}
private:
Brush br;
Background bg;
CompundShape cshape;
};

对于外部调用者来说,只需调用drawBadget(),将徽章复杂图形变量作为形式参数传入函数,即可完成各种徽章的绘制。

享元模式(Flyweight)

享元模式是通过共享对象状态,使得对象不必保存所有的状态,从而达到在内存中载入更多的对象。

常见应用场景是游戏制作场景,游戏中最常见的元素是粒子系统,每一个粒子都有自己的坐标,颜色,状态等信息,如果每一个粒子对象都存储所有的信息,那么在实际游戏场景中将会对内存需求巨大,几十万个粒子就会导致系统内存被用完,从而导致OOM,为了解决这个问题,可以从对象的类来分析,提取其中的常量和变量,对于常量使用共享对象存储。

如下:

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
class Background {};

class Color {};

class Flyweight {
public:
void draw() {
// 绘制粒子
}
private:
Background bg;
Color cr;
};

class FlyweightFactory {
public:
Flyweight* getFlyweight(state s) {
auto f = cache.find(s);
if (f != nullptr) return f;
return new Flyweight();
}
private:
Cache cache;
};

享元模式使用前还需要进行初始化,以便将常量对象存储到享元工厂的cache中。

1
2
3
4
5
6
7
8
9
// 初始化享元工厂
auto particle1 = new Flyweight(1); // 静态粒子1
auto particle2 = new Flyweight(2); // 静态粒子2
// ...

FlyweightFactory::getInstance().addFlyweight(particle1);
FlyweightFactory::getInstance().addFlyweight(particle2);

auto particle = FlyweightFactory::getInstance().getFlyweight(-1); // 普通动态粒子

代理模式(Proxy)

代理模式提供对对象访问的占位,控制对原始对象的访问,可以做到在外界对对象请求操作前后添加额外处理。

还是以绘图案例为例,这里绘制的是图片,这些图片以tag为唯一名称存储在数据库中,每次绘制需要从数据库获取相应的图片,再进行绘制,但是,如果绘制的图形复杂,包含的图片较多的时候,绘制效率会很低,绘制界面就会出现绘制延迟的现象,根本原因是从数据库获取图片的累加时间太长了。所以解决这种问题,就要用到代理器的模式,如下:

Proxy

这里的代理器在获取到图片的时候,会对已经获取的图片做缓存,这样下次再次获取该图片,就可以立即返回该图片,从而大大减少绘制时间,使绘制效率变高。

案例代码:

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 Image {
public:
std::vector<std::string> listImages() = 0;
std::vector<uint8_t> getImage(std::string&) = 0;
};

class DBImage : public Image {
public:
std::vector<std::string> listImages() {
auto result = db.findAll();
// 数据库查询返回所有的图片的tag列表
};
std::vector<uint8_t> getImage(std::string&) {
// 返回对应tag的图片数据
};
private:
DB db;
};

class CachedImage : public Image {
public:
CachedImage(DBImage& dbi) {}
std::vector<std::string> listImages() {
auto result = cache.findAll(); // 缓存查找
if (!result.empty()) {
return result;
}
result = service.findAll(); // 原始服务查找
cache.store(result);
return result;
};
std::vector<uint8_t> getImage(std::string& tag) {
auto img = cache.find(tag); // 缓存查找图片
if (!img.empty()) {
return img;
}
img = service.find(tag); // 原始服务获取图片
cache.store(tag, img);
return img;
};
private:
DBImage service;
Cache cache;
};

实际使用时,直接调用代理器即可:

1
2
3
4
5
DBImage dbi("mongo://xxxx");
CachedImage ci(dbi);

auto result = ci.listImages();
auto image = ci.getImage(result[0]); // 第一个tag的图片

CacheImage > CacheImage中使用的Cache,根据实际应用场景,可以是中间件存储,例如:Memcached,redis等。

MVC模式(MVC)

MVC分别代表:Model-View-Controller,这种方式应用于程序的分层开发,减少数据,视图界面,和逻辑控制之间的耦合,使开发维护更加清晰。

这种模式在传统的MFC开发框架中广泛使用。下面演示一个富文本编辑器的MVC设计。

MVC

代码如下:

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
class Model {};

class View {};

class Controller {
public:
void setView() = 0;
void setModel() = 0;
};

class Document : public Model {
public:
Document(Controller* ctrl) {
this->ctrl = ctrl;
}
private:
Controller* ctrl;
};

class RichEditCtrl : public Controller {
public:
void setView(View* v) {
this->view = v;
}
void setModel(Model* m) {
this->model = m;
}
private:
View* view;
Model* model;
};

class RichEditView : public View {
public:
View(Controller* ctrl) {
this->ctrl = ctrl;
}
private:
Controller* ctrl;
};

构造MVC时如下:

1
2
3
4
5
6
7
8
9
10
11
12
RichEditCtrl ctrl;
RichEditView view(&ctrl);
Document doc(&ctrl);
ctrl.setView(&view);
ctrl.setModel(&doc);

// 视图的更新通知控制器
ctrl.notify();
// 控制器更新数据模型
doc.update();
// 模型更新完后刷新视图
view.refresh();

欢迎关注我的其它发布渠道