深入设计模式 C++ 版
无论是算法还是设计模式,计算机的很多设计思想都是从生活当中来,而最终回归与生活当中去解决生活当中实际生产当中的问题,因此无论是算法还是设计模式与实际生活联系,就会发现很多妙处。
本内容当中的所有关于设计模式的案例以及讲解,全部基于 深入设计模式 该网站,有关具体的设计思想详细解答,读者可迁移到此处阅读学习。感谢亚历山大·什韦茨 团队对如此精美的网站开源分享。
创建型模式
构造器模式
生成器模式也叫做 Builder 构造模式,从名字就可以联想到该设计思想必定是为了便利或更清晰的创建某一种东西而产生的。 可以假设,有一个较为复杂庞大的业务,这使的业务本身所具有的对象内部的私有成员属性就非常的庞大复杂。 因此在对其进行构造时需要对诸多成员变量和嵌套对象进行繁复的初始化工作。 这些初始化代码通常深藏于一个包含众多参数且让人基本看不懂的构造函数中; 甚至还有更糟糕的情况, 那就是这些代码散落在客户端代码的多个位置。
原文详细解读可移步骤到此处 : 深入设计模式-构造器模式
场景重现
通过如图所示的案例可以很好的理解类似的场景,假设我们现在要建造一个房子 House ,在建造一个简单房子的时候,我们需要对该房子安装窗户、墙面、大门等基础设备,以及给房子一个屋顶。同时如果想让建造的房子更加的精美,根据不同的需求,我们可能还需要添加一些地暖,游泳池,吉祥物,绿植等,就如图中的 HouseWithGarage ,HouseWithFancyStatues ,HouseWithSwimmingPool ,HouseWithGarden 。
那么面对这些需求,我们应该怎么进行构建呢?
-
首先很容易就可以想到也根据图示,最简单的就是基类的继承,也就是根据基础类
House去扩展其他需求的子类,甚至在基础类纸上再去抽象出一个同一类别的基础类比如别墅类,楼房类,四合院类等,很明显这样的扩展很生硬,很不灵活。同时如果对基类增加了某一共有的属性,则会影响到整个基于该父类的子类内部,并且随着业务的扩展,子类内部也属性层级关系也会变得非常的庞大复杂。 -
而另一种方式就是一次性将基类
House内部的属性完整,也是说House基类当中一开始就包含了别墅类,楼房类,四合院类的所有属性,并且通过调用父类 House 的全参构造完成属性的用户填充,而对于部分子类没有的属性,则默认填充为 NULL 即可。但这样同样的也很容易看到,在通常的情况下,子类的某一些参数是未被使用的,这使的对于 构造参数的调用十分不简洁 , 而我们在设计一个类的时候,首要应该遵循的原则就是做到方法构造,应该尽可能的能说明该方法的具体功能。
解决方案
而构造器或者说生成器,就是为了解决此类业务场景的一种设计思想。其具体做法就是,生成器模式建议将对象构造代码从产品类中抽取出来, 并将其放在一个名为生成器的独立对象中。
想必读者们看到这张图的时候,立刻就明白构造器模式的核心思想,其实就是将内部的复杂的属性构造,通过一系列的调用步骤,单独通过一个生成器进行构建,同时调用返回对象方法,获取构建完成的对象。
比如,你需要创建一个 别墅类 对象,只需要按照实际需求,调用特点的生成器方法即可,对需要构建的属性无需调用。
然后当需要另外的一种情况,比如你需要创造不同形式的产品时,比如说同一种基类但内部的属性值可能并不相同,这也就是代表着可能我需要通过 buildDoors() 方法返回一个 门 这样的具体属性,但是我需要的门的类型并不相同,也许 别墅类 需要的是一个 别墅门 而 楼房类 需要的则是一个 楼房门 ,虽然都是调用的 buildDoors() 但所需要的具体对象并不相同 。对于这样的产品需要,你可以创建多个不同的生成器, 用不同方式实现一组相同的创建步骤。 然后你就可以在创建过程中使用这些生成器 (例如按顺序调用多个构造步骤) 来生成不同类型的对象。
主管类
在前面的生成器类调用过程当中,可以发现似乎内部的属性调用好像没有顺序方面的约束,当然你可以在编码的过程当中显示的将调用的步骤内嵌到你的代码当中,但是这样的嵌入使得编码依赖关系变得更加紧密,而不利于后续业务的变动或扩展,因此我们可以通过一个用于约束构建规范或顺序的类,对其构建生成的过程进行约束管理,这也就是主管类的作用。
而这样的设计,对于客户端用户而言,我们隐藏了内部的调用细节。只是给用户暴露必要的构建接口,使得结构上更加简洁清晰。
UML 关系基础类图
伪代码的实现
下面关于生成器模式的例子演示了你可以如何复用相同的对象构造代码来生成不同类型的产品——例如汽车 (Car)——及其相应的使用手册 (Manual)。
代码实现
#include <iostream>
#include <string>
using namespace std;
//一个汽车有 座位、引擎、里程表,以及 GPS
class Car {
public:
std::string seats;
std::string engline;
std::string tripComputer;
std::string gps;
};
//用户手册当中有对其 座位、引擎、里程表,以及 GPS 的功能介绍
class Manual {
public:
std::string seats;
std::string engline;
std::string tripComputer;
std::string gps;
};
//提供一个抽象的生成器
class Builder {
public:
virtual void reset() = 0;
virtual void setSeats(const string& seats) = 0;
virtual void setEngline(const string& engline) = 0;
virtual void setTripComputer(const string& tripComputer) = 0;
virtual void setGps(const string& gps) = 0;
};
//创建一个汽车生成器
class CarBuilder :public Builder {
private:
Car car;
public:
// 通过 Builder 继承
void reset() override {
this->car = Car();
}
void setSeats(const string& seats) override {
this->car.seats = seats;
}
void setEngline(const string& engline) override {
this->car.engline = engline;
}
void setTripComputer(const string& tripComputer) override {
this->car.tripComputer = tripComputer;
}
void setGps(const string& gps) override {
this->car.gps = gps;
}
Car getProduct() {
return this->car;
}
};
class CarManualBuilder :public Builder {
private:
Manual manual;
public:
// 通过 Builder 继承
void reset() override {
this->manual = Manual();
}
void setSeats(const string& seats) override {
this->manual.seats = seats;
}
void setEngline(const string& engline) override {
this->manual.engline = engline;
}
void setTripComputer(const string& tripComputer) override {
this->manual.tripComputer = tripComputer;
}
void setGps(const string& gps) override {
this->manual.gps = gps;
}
Manual getProduct() {
return this->manual;
}
};
class Director {
public:
void constructSportsCar(Builder& builder) {
builder.reset();
builder.setEngline("Sports 引擎");
builder.setGps("Sports 导航");
builder.setSeats("Sports 座位");
builder.setTripComputer("Sports 记程器");
}
void constructSUV(Builder& builder) {
builder.reset();
builder.setEngline("SUV引擎");
builder.setGps("SUV导航");
builder.setSeats("SUV座位");
builder.setTripComputer("SUV记程器");
}
};
int main() {
Director director = Director();
CarBuilder carBuidler = CarBuilder();
director.constructSportsCar(carBuidler);
CarManualBuilder carManunlBuilder = CarManualBuilder();
director.constructSUV(carManunlBuilder);
cout << carBuidler.getProduct().gps << endl;
cout << carManunlBuilder.getProduct().gps << endl;
}
原型模式
原型模式的核心就是通过一个已经创建的实例为原型,通过复制该原型对象来创建一个与原型对象一样的新对象。
实际场景
在实际的生产当中,原型模式主要用于解决的问题大多数都是属于,创建对象的成本比较高,比如一个对象需要进过大量的复杂计算才能被创建,或者该对象是通过 RPC 接口,或数据库而获取创建的,这种情况下使用原型模式,可以很好的降低在运行过程当中对于对象创建所带来的资源占用。
模式结构
原型模式,比较简单其只包含以下几个角色:
- 抽象的原型类:用于声明和克隆方法的接口,是所有的原型类的公共父类,里面提供了克隆对象的抽象方法。
- 具体原型类:实现类抽象原型类的当中的抽象方法,通过实现的抽象方法返回一个自己一样的新对象。
- 客户端类:让一个原型对象克隆自己的从而产生一个新的对象,由于客户端是根据抽象原型类进行接口调用的,所以用户可以自定义的任何的已经实现抽象方法的具体原型类对象。
UML 关系类图
在 Java 当中其实本身是有具体的克隆接口 Cloneable ,只要在自己的原型类上实现该接口,调用父类的 super.clone() 方法就可以克隆原型对象。需要注意的是这种直接调用 super.clone() 的克隆方法并不会克隆,对象成员属性指向的对象值,因此这种克隆属于浅克隆,如果要连同对象的属性也要一起克隆,则需要自定义实现的克隆方法,可以通过对象的序列化写入与读取,从而创建一个完整的对象。
代码实现
下面将用 C++ 实现一个关于,几何副本进行对象克隆创建,结构类图如下。
#include <iostream>
using namespace std;
class Shape
{
public:
int x, y;
int color;
public:
Shape() = default;
Shape(Shape* shape)
{
this->x = shape->x;
this->y = shape->y;
this->color = shape->color;
}
virtual Shape* clone() = 0;
int getX()
{
return this->x;
}
int getY()
{
return this->y;
}
int getColor()
{
return this->color;
}
};
class Rectangle :public Shape
{
public:
int witdth;
int height;
public:
Rectangle() = default;
Rectangle(Rectangle* rectangle) :Shape(rectangle)
{
this->witdth = rectangle->witdth;
this->height = rectangle->height;
}
// 通过 Shape 继承
Shape* clone() override
{
return new Rectangle(this);
}
int getWitdth()
{
return this->witdth;
}
int getHeight()
{
return this->height;
}
};
class Circle :public Shape
{
public:
int radius;
public:
Circle() = default;
Circle(Circle* circle) :Shape(circle)
{
this->radius = circle->radius;
}
// 通过 Shape 继承
Shape* clone() override
{
return new Circle(this);
}
int getRadius()
{
return this->radius;
}
};
class Application
{
public:
static Shape* clone(Shape* shape)
{
return shape->clone();
}
};
int main()
{
Application application;
Rectangle rectangle;
rectangle.witdth = 100;
rectangle.x = 200;
rectangle.height = 200;
Shape* copyShape1 = application.clone(&rectangle);
Shape* copyShape2 = application.clone(&rectangle);
cout << copyShape1->x << endl;
cout << copyShape2->x << endl;
}
这里可以看到,由于原型对象 recangle 克隆生成的两个 Shape 对象指针所指向的地址不相同。