设计模式(二):结构型模式

适配器模式

软件设计的时候,有时我们现有的类就能够满足某一个功能需求了,但是现有的类提供的接口却并不是客户所期望的,如现有类的方法名和客户要求的不一致等。这时可以采用适配器(Adapter)模式对原有的类进行包装(Wrap),从而适配客户的需求。

这个模式比较好理解,就是通过包装来修改已有类的接口,就好像电源适配器一样。需要注意的是适配器(Adapter)类是持有被适配类(Adaptee)的实例的,不存在继承关系。

下面用一个例子来说明,假如我们有一个可以输出两种修饰符号的类:

1
2
3
4
5
6
7
8
9
10
11
12
class Banner {
public:
explicit Banner(std::string content) : content_(std::move(content)){}
void showWithParen() {
std::cout << "(" << content_ << ")" << std::endl;
}
void showWithAster() {
std::cout << "*" << content_ << "*" << std::endl;
}
private:
std::string content_;
};

但是客户的需求是实现强语气和弱语气两种输出方式:

1
2
3
4
5
6
7
// define output interface
class Print {
public:
virtual void printWeak() = 0;
virtual void printStrong() = 0;
virtual ~Print() = default;
};

我们刚好通过一个适配器来将 Banner 类的方法进行修正:

1
2
3
4
5
6
7
8
9
10
11
12
13
// As adapter of banner, make banner adapt to "Print interface"
class BannerAdapter : public Print {
public:
explicit BannerAdapter(std::string content) : banner_(new Banner(std::move(content))){}
void printStrong() override {
banner_->showWithParen();
}
void printWeak() override {
banner_->showWithAster();
}
private:
std::shared_ptr<Banner> banner_;
};

使用如下:

1
2
3
4
5
6
7
8
int main() {
std::shared_ptr<Print> banPrinter = std::make_shared<BannerAdapter>("Hello");
banPrinter->printStrong();
banPrinter->printWeak();
}

// (Hello)
// *Hello*

桥接模式

桥接模式(Bridge Pattern)将抽象部分实现部分分离,使它们可以独立变化。

桥接模式定义比较难理解,这里举一个简单的例子说明为什么要有桥接模式这种设计。如果我们需要表示不同颜色和不同形状的物体,如红色、绿色以及圆形和三角形,倘若我们只使用形状类 Shape 作为基类进行设计,我们需要定义 4 个子类来分别表示红色圆形、绿色圆形、红色三角形和绿色三角形。倘若有更多的颜色和形状,我们的子类数量将会乘性的增加。

桥接模式通过将颜色和形状变化的维度进行桥接,可以解决这个问题。具体的实现思路是,将颜色定义成一个接口,形状类持有这个接口。那么颜色这个维度可以和形状解耦单独变化。桥接模式又称为柄体(Handle and Body)模式。

上面的例子实现如下,首先是颜色接口的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Color {
public:
virtual ~Color() = default;
virtual void drawWithColor(const std::string& str) = 0;
};

class Red : public Color {
public:
void drawWithColor(const std::string &str) override {
std::cout << "draw:" << str << " with red color" << std::endl;
}
};

class Green : public Color {
public:
void drawWithColor(const std::string &str) override {
std::cout << "draw:" << str << " with green color" << std::endl;
}
};

然后定义形状抽象类,并持有一个颜色的接口:

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 Shape {
public:
explicit Shape(const std::shared_ptr<Color>& color) : color_(color) {}
virtual ~Shape() = default;

void setColor(const std::shared_ptr<Color>& color) {
color_ = color;
}
virtual void draw() = 0;

protected:
std::shared_ptr<Color> color_; // handle interface Color to get its methods
};

class Circle : public Shape {
public:
explicit Circle(const std::shared_ptr<Color>& color) : Shape(color) {}
void draw() override {
color_->drawWithColor("circle");
}
};

class Triangle : public Shape {
public:
explicit Triangle(const std::shared_ptr<Color>& color) : Shape(color) {}
void draw() override {
color_->drawWithColor("triangle");
}
};

这样的话只需要在 Shape 类中调用 Color 接口的方法,不需要管其实际的子方法。

使用如下:

1
2
3
4
5
6
7
8
9
int main() {
std::shared_ptr<Shape> circle = std::make_shared<Circle>(std::make_shared<Red>());
circle->draw();
std::shared_ptr<Shape> tri = std::make_shared<Triangle>(std::make_shared<Green>());
tri->draw();
}

// draw:circle with red color
// draw:triangle with green color

装饰模式

装饰模式(Decorator Pattern)通过对原有类进行包装来扩展原有类的行为,而不会改变原有类的结构,实际上就是原有类的一个包装(Wapper)。

实际上扩展一个类的功能可以采用继承或组合(持有它的实例)两种方法,一般而言需要避免继承方法,而采用组合方法,这里的装饰模式就是采用组合方法来扩展原有类的功能。当然这里一般也会将装饰器定义成原类的子类。

举例如下,原有的类是颜色类:

1
2
3
4
5
6
7
8
9
10
11
12
class Shape {
public:
virtual ~Shape() = default;
virtual void draw() = 0;
};

class Circle : public Shape {
public:
void draw() override {
std::cout << "draw circle" << std::endl;
}
};

为了对其进行功能扩展,实现一个它的装饰类:

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
class ShapeDecorator : public Shape{
public:
explicit ShapeDecorator(const std::shared_ptr<Shape>& shape) : shape_(shape) {}
~ShapeDecorator() override = default;
void draw() override {
shape_->draw();
}

protected:
std::shared_ptr<Shape> shape_; // decorate shape
};

class RedShapeDecorator : public ShapeDecorator {
public:
explicit RedShapeDecorator(const std::shared_ptr<Shape>& shape) : ShapeDecorator(shape) {}
void draw() override {
shape_->draw();
setRedBorder();
}

private:
void setRedBorder() {
std::cout << "set red border" << std::endl;
}
};

使用时,调用装饰类的方法:

1
2
3
4
5
6
7
8
9
10
int main() {
std::shared_ptr<Shape> circle = std::make_shared<Circle>();
std::shared_ptr<ShapeDecorator> redCircle = std::make_shared<RedShapeDecorator>(circle);
// std::shared_ptr<Shape> redCircle = std::make_shared<RedShapeDecorator>(circle);
redCircle->draw();
return 0;

// draw circle
// set red border
}

外观模式

一个系统可能会存在若干个子系统,为了对外简洁和使用简单,可以使用一个外观(Facade)对象作为系统的门面,这样可以将各个子系统隐藏起来。外观模式(Facade Pattern)比较容易理解,就是为了客户使用方便而定义的一个接口类。

若系统有若干形状的子系统:

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

class Circle : public Shape {
public:
void draw() override {
std::cout << "draw circle" << std::endl;
}
};

class Rectangle : public Shape {
public:
void draw() override {
std::cout << "draw rectangle" << std::endl;
}
};

class Square: public Shape {
public:
void draw() override {
std::cout << "draw square" << std::endl;
}
};
```

为了将这些方法一起对外提供,定义一个 Facade,

``` C++
class ShapeFacade {
public:
ShapeFacade() : circle_(new Circle), rectangle_(new Rectangle), square_(new Square) {}
void drawCircle() {
circle_->draw();
}
void drawRectangle() {
rectangle_->draw();
}
void drawSquare() {
square_->draw();
}

private:
std::shared_ptr<Shape> circle_;
std::shared_ptr<Shape> rectangle_;
std::shared_ptr<Shape> square_;
};
1
2
3
4
5
6
7
8
9
10
int main() {
auto facade = std::make_shared<ShapeFacade>();
facade->drawCircle();
facade->drawRectangle();
facade->drawSquare();
return 0;
// draw circle
// draw rectangle
// draw square
}

享元模式

享元模式(Flyweight Pattern)通过共享技术实现系统中相同对象的重用,一般是小对象,从而减少创建对象的数量,以减少内存占用和提高性能。

享元模式一般提供一个工厂类,这个工厂保存一个 Map, 工厂接受 Map 的键返回对象,当多次请求同一个键时可以返回相同的对象,从而实现对象的共享。一个比较常见的例子就是 Java 的字符串对象,当创建字符串时,若字符串缓存池中有该字符串则直接将其返回,否则创建该字符串并将其加入缓存池中。

下面通过形状和形状工厂的例子来说明问题,首先是形状类和 Circle 子类:

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

class Circle : public Shape {
public:
explicit Circle(std::string color) : color_(std::move(color)) {}
void setX(int x) { x_ = x; }
void setY(int y) { y_ = y; }
void setRadius(int radius) { radius_ = radius; }

void draw() override {
std::cout << "[Draw circle] x: " << x_ << ", y: "
<< y_ << ", radius: " << radius_ << ", color: " << color_ << std::endl;
}
private:
int x_ = 0;
int y_ = 0;
int radius_ = 10;
std::string color_;
};

然后定义一个工厂类,为了重用 Circle 对象,工厂类维护一个哈希表,保存已经创建的对象,键值为字符串(形状的颜色),当请求的对象存在时直接返回,不存在时创建新对象并将其加入哈希表中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class ShapeFactory {
public:
typedef std::shared_ptr<Circle> CirclePtr;
static CirclePtr getCircle(const std::string& color) {
auto it = circleMap_.find(color);
if (it == circleMap_.end()) {
circleMap_[color] = std::make_shared<Circle>(color);
std::cout << "create circle of color:" << color << std::endl;
}
return circleMap_[color];
}
private:
static std::unordered_map<std::string, CirclePtr> circleMap_;
};

std::unordered_map<std::string, ShapeFactory::CirclePtr> ShapeFactory::circleMap_;

使用时根据颜色来获取 Circle 对象,这里假如有 5 种颜色的对象,并随机的取 20 个对象出来使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
int main() {
std::vector<std::string> colors {"Red", "Blue", "Green", "White", "Black"};
std::default_random_engine e(std::random_device{}());
std::uniform_int_distribution<size_t> u(0, 4);
for (int i=0; i<20; ++i) {
auto circle = ShapeFactory::getCircle(colors[u(e)]);
circle->setX(e() % 100);
circle->setY(e() % 100);
circle->setRadius(100);
circle->draw();
}
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
create circle of color:Red
[Draw circle] x: 70, y: 51, radius: 100, color: Red
create circle of color:Blue
[Draw circle] x: 65, y: 79, radius: 100, color: Blue
[Draw circle] x: 42, y: 19, radius: 100, color: Red
create circle of color:Black
[Draw circle] x: 51, y: 76, radius: 100, color: Black
[Draw circle] x: 89, y: 44, radius: 100, color: Red
create circle of color:Green
[Draw circle] x: 73, y: 81, radius: 100, color: Green
[Draw circle] x: 66, y: 59, radius: 100, color: Blue
[Draw circle] x: 18, y: 78, radius: 100, color: Green
create circle of color:White
[Draw circle] x: 62, y: 30, radius: 100, color: White
[Draw circle] x: 22, y: 27, radius: 100, color: White
[Draw circle] x: 2, y: 89, radius: 100, color: Green
[Draw circle] x: 49, y: 44, radius: 100, color: Green
[Draw circle] x: 35, y: 44, radius: 100, color: Black
[Draw circle] x: 61, y: 43, radius: 100, color: White
[Draw circle] x: 78, y: 55, radius: 100, color: White
[Draw circle] x: 18, y: 24, radius: 100, color: Black
[Draw circle] x: 54, y: 21, radius: 100, color: White
[Draw circle] x: 94, y: 22, radius: 100, color: White
[Draw circle] x: 16, y: 23, radius: 100, color: Black
[Draw circle] x: 7, y: 35, radius: 100, color: White

可以看到只创建了 5 次对象,大量的对象被复用了,这就是享元模式的作用。

代理模式

代理模式(Proxy Pattern)在之前的博客中已经介绍过了:Spring框架(三):动态代理和AOP,AOP 通过动态代理的方式实现了对原有功能的扩充,并且不会影响到原有类的结构。代理模式通过称为代理的第三方来间接提供服务。从而原有的服务进行扩充或隐藏。

这里还是把之前的例子拿来用,但是是静态代理的实现,首先还是有一个能够添加和删除商品的服务,并有一个实现了该接口的子类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class GoodService {
public:
virtual ~GoodService() = default;
virtual void addGood() = 0;
virtual void delGood() = 0;
};

class OrangeService : public GoodService {
public:
void addGood() override {
std::cout << "add good" << std::endl;
}
void delGood() override {
std::cout << "del good" << std::endl;
}
};

为了对 OrangeService 进行功能扩充,即在每次方法调用之前进行记录。可以使用一个代理类来对原有类方法进行增强:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// proxy of OrangeService
class OrangeServiceProxy : public GoodService {
public:
OrangeServiceProxy() : orangeService_(new OrangeService) {}
void addGood() override {
log("before addGood");
orangeService_->addGood();
}
void delGood() override {
log("before delGood");
orangeService_->delGood();
}
private:
static void log(const std::string& str) {
std::cout << str << std::endl;
}
std::shared_ptr<OrangeService> orangeService_;
};

代理类也继承自 GoodService,并持有一个 OrangeService 的实例。

使用的时候通过代理类而不是原有类:

1
2
3
4
5
int main() {
std::shared_ptr<GoodService> service = std::make_shared<OrangeServiceProxy>();
service->addGood();
service->delGood();
}

参考文献

  1. https://design-patterns.readthedocs.io/zh_CN/latest/structural_patterns/structural.html
  2. https://www.runoob.com/design-pattern/design-pattern-tutorial.html

此外,本文的代码可以在https://github.com/xiaoqieF/Design-Pattern-notes中找到。