“对象创建”模式:
通过“对象创建”模式来绕开new,来避免对象创建(new)过程中所导致的紧耦合(依赖具体类),从而支持对象创建的稳定。它是接口抽象之后的第一步工作。
典型模式:Factory Method、Abstract Factory、Prototype、Builder
工厂方法(Factory Method)
工厂方法:定义一个用于创建对象的接口,让子类决定实例化哪一个类,工厂方法使得一个类的实例化延迟(目的:解耦,手段:虚函数)到子类。
错误示范:如下代码,不同的文件可能会有不同的切割方式,通过new的方式,会立刻使得该行依赖于具体细节。
class ISplitter{
public:
virtual void split()=0;
virtual ~ISplitter(){}
};
class BinarySplitter : public ISplitter{
};
class TxtSplitter : public ISplitter{
};
class PictureSplitter : public ISplitter{
};
class MainForm{
public:
void Button01_Click(){
ISplitter *splitter = new BinarySplitter(); // BinarySplitter是具体细节
splitter->split(); // 执行文件的切割打包
}
};
正确示范:
// ISplitter及子类部分同上
class SplitterFactory{ // 工厂基类
public:
virtual ISplitter* CreateSplitter() = 0;
virtual ~SplitterFactory() {}
};
//具体工厂
class BinarySplitterFactory : SplitterFactory{
public:
virtual ISplitter* CreateSplitter() {
return new BinarySplitter();
}
virtual ~BinarySplitterFactory() {}
};
class TxtSplitterFactory : SplitterFactory{
public:
virtual ISplitter* CreateSplitter() {
return new TxtSplitter();
}
virtual ~TxtSplitterFactory() {}
};
class PictureSplitterFactory : SplitterFactory{
public:
virtual ISplitter* CreateSplitter() {
return new PictureSplitter();
}
virtual ~PictureSplitterFactory() {}
};
class MainForm{
SplitterFactory *factory;
public:
MainForm(SplitterFactory *facPtr){// 相当于把依赖从Mainform中移除,依赖可以放到更局部的地方
this->factory = facPtr;
}
void Button01_Click(){
ISplitter *splitter = factory->CreateSplitter();//多态new
splitter->split(); // 执行文件的切割打包
}
};
总结:
1. 工厂方法用于隔离类对象的使用者和具体类型之间的耦合关系,面对一个经常变化的具体类型,紧耦合关系(new)会导致软件的脆弱
2. 工厂方法通过面向对象的手法,将所要创建具体对象的工作延迟到子类,从而实现一种扩展(而非更改)的策略,较好地解决了这种紧耦合关系
3. 工厂方法解决“单个对象”的需求变化,缺点在于创建方法相同(参数要相同,即CreateSplitter的参数)。
抽象工厂(Abstract Factory)
在软件系统中,经常面临着“一系列相互依赖的对象”的创建工作;同时,由于需求的变化,往往存在更多系列对象的创建工作。
如一个完整功能的实现,需要A、B、C类的协同参与,但是A、B、C类又有不同的版本。如第一版A_1, B_1, C_1、第二版A_2, B_2, C_2。当你为他们实现工厂模式的时候,设计的工厂基类时就可以将A、B、C三个类的创建方法(三个虚函数)都写在一个基类里。然后设计1、2两版的工厂子类。
抽象工厂模式主要在于应对“新系列”的需求变动,其缺点在于难以应对“新对象”的需求变动(如新增D类)。
原型模式(Prototype)
使用原型实例指定创建对象的种类,然后通过拷贝这些原型来创建新的对象。
在软件系统中,经常面临着“某些结构复杂的对象”的创建工作,由于需求的变化,这些对象经常面临着剧烈的变化,但是它们却拥有比较稳定一致的接口。这时可以通过clone的方式创建对象,隔离普通构造创建对象过程中可能存在的变化。
通常使用工厂模式,会写面向功能抽象基类,和工厂基类。然后再分别写功能子类和工厂子类。
//抽象类基类
class Isplitter{
public:
virtual void split()=0;
virtual ~Isplitter() {}
};
//工厂基类
class SplitterFactory{
public:
virtual ISplitter *CreateSplitter()=0;
virtual ~SplitterFactory() {}
};
而原型模式则是把两者写到一个基类里,使用虚函数实现clone函数,在clone函数里借助拷贝构造函数来,创建类对象。
class ISplitter{
virtual void split()=0;
virtual ISplitter *clone() =0;
virtual ~Isplitter() {}
};
class BinarySplitter{
public:
//需要实现拷贝构造函数
virtual ISplitter *clone(){
return new BinarySplitter(*this);
}
};
// 使用对象的时候不能使用原型对象,只能使用clone出来的对象
class MainForm{
ISplitter *prototype;
public:
MainForm(ISplitter *p){
this->prototype = p; //不能直接使用原型对象
}
void Button01_Click(){
ISplitter *splitter = prototype->clone();//使用clone创建
splitter->split(); // 执行文件的切割打包
}
};
- 原型模式同样用于隔离类对象的使用者和具体类型(易变型)之间的耦合关系,它同样要求这些“易变类”拥有“稳定的接口”
- 原型模式对于“如何创建易变类的实体对象”采用“原型克隆”的方法来做,它使得我们可以非常灵活地动态创建“拥有某些稳定接口”的新对象————所需工作仅仅是注册一个新类的对象(即原型),然后再任何需要的地方Clone。
- 原型模式中的Clone方法可以利用某些框架中的序列化来实现深拷贝。
构建器(Builder)
将一个复杂对象的构建与其表示相分离,使得同样的构建过程(稳定)可以创建不同的表示(变化)。
(相当于模板方法用于创建对象)
class House{
public:
void Init(){
this->BuildPart1();
for(int i = 0; i < 4; ++i){
this->BuildPart2();
}
bool flag = this->BuildPart3();
if (flag){
this->BuildPart4();
}
this->BuildPart5();
}
~virtual ~House(){}
protected:
virtual void BuildPart1()=0;
virtual void BuildPart2()=0;
virtual void BuildPart3()=0;
virtual void BuildPart4()=0;
virtual void BuildPart5()=0;
};
class StoneHouse: public House{
protected:
virtual void BuildPart1(){
}
virtual void BuildPart2(){
}
virtual void BuildPart3(){
}
virtual void BuildPart4(){
}
virtual void BuildPart5(){
}
};
int main(){
House *pHouse = new StoneHouse();
pHouse->Init();
}
上面的写法已经实现了基本的Builder模式,但是一般为了避免House类太过复杂,会将其功能进行拆分
class House{
// ... 状态和数据
};
class HouseBuilder{
public:
HouseBuilder(House * p) pHouse(p) {}
void Init(){
this->BuildPart1();
for(int i = 0; i < 4; ++i){
this->BuildPart2();
}
bool flag = this->BuildPart3();
if (flag){
this->BuildPart4();
}
this->BuildPart5();
}
House * GetResult(){
return pHouse;
}
virtual ~HouseBuilder() {}
protected:
House *pHouse;
virtual void BuildPart1()=0;
virtual void BuildPart2()=0;
virtual void BuildPart3()=0;
virtual void BuildPart4()=0;
virtual void BuildPart5()=0;
};
class StoneHouse: public House{
// ... 状态和数据
};
class StoneHouseBuilder: public HouseBuilder{
protected:
virtual void BuildPart1(){
}
virtual void BuildPart2(){
}
virtual void BuildPart3(){
}
virtual void BuildPart4(){
}
virtual void BuildPart5(){
}
};
int main(){
House *pHouse = new StoneHouse();
HouseBuilder *pHousebuild = new StoneHouseBuilder(pHouse);
pHousebuild->Init();
}
- Builder模式主要用于“分步骤构建一个复杂的对象”。在这其中“分步骤”是一个稳定的算法,而复杂对象的各个部分则经常变化。
- 变化点在哪里,封装哪里————Builder模式主要在于应对“复杂对象各个部分”的频繁需求变动。其缺点在于难以应对“分步骤构建算法”的需求变动
- 在Builder模式中,要注意不同语言中构造器调用虚函数的差别(C++的构造函数不要调用虚函数,否则只会静态调用当前类的实现,而不是子类的重写,C#则可以)