【设计模式】 模式PK:抽象工厂模式VS建造者模式
1、概述
抽象工厂模式实现对产品家族的创建,一个产品家族是这样的一系列产品:具有不同分类维度的产品组合,采用抽象工厂模式则是不需要关心构建过程,只关心什么产品由什么工厂生产即可。而建造者模式则是要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新产品,两者的区别还是比较明显的。
现代化的汽车工厂能够批量生产汽车(不考虑手工打造的豪华车)。不同的工厂生产不同的汽车,宝马工厂生产宝马牌子的车,奔驰工厂生产奔驰牌子的车。车不仅具有不同品牌,还有不同的用途分类,如商务车Van,运动型车SUV等,我们按照两种设计模式分别实现车辆的生产过程。
2、抽象工厂模式生产车辆
2.1 类图
按照抽象工厂模式,首先需要定义一个抽象的产品接口即汽车接口,然后宝马和奔驰分别实现该接口,由于它们只具有了一个品牌属性,还没有定义一个具体的型号,属于对象的抽象层次,每个具体车型由其子类实现,如R系列的奔驰车是商务车,X系列的宝马车属于SUV,我们来看类图。
在类图中,产品类很简单,我们从两个维度看产品:品牌和车型,每个品牌下都有两个车型,如宝马SUV,宝马商务车等,同时我们又建造了两个工厂,一个专门生产宝马车的宝马工厂BMWFactory,一个是生产奔驰车的奔驰车生产工厂BenzFactory。当然,汽车工厂也有两个不同的维度,可以建立这样两个工厂:一个专门生产SUV车辆的生产工厂,生产宝马SUV和奔驰SUV,另外一个工厂专门生成商务车,分别是宝马商务车和奔驰商务车,这样设计在技术上是完全可行的,但是在业务上是不可行的,为什么?这是因为你看到过有一个工厂既能生产奔驰SUV也能生产宝马SUV吗?这是不可能的,因为业务受限,除非是国内的山寨工厂。
2.2 代码
2.2.1 产品类
class CICar
{
public:
CICar(){};
~CICar(){};
//汽车的生产商, 也就是牌子
virtual string msGetBand() = ;
virtual string msGetModel() = ;
};
在产品接口中我们定义了车辆有两个可以查询的属性:品牌和型号,奔驰车和宝马车是两个不同品牌的产品,但不够具体,只是知道它们的品牌而已,还不能够实例化,因此还是一个抽象类。
2.2.2 抽象宝马车
class CAbsBMW :public CICar
{
public:
CAbsBMW() { msBand = "宝马汽车"; }
~CAbsBMW(){}; //宝马车
string msGetBand(){ return msBand; } // 型号由具体的实现类实现
virtual string msGetModel() = ; private:
string msBand;
};
抽象产品类中实现了产品的类型定义,车辆的型号没有实现,两实现类分别实现商务车和运动型车。
2.2.3 宝马商务车
class CBMWVan : public CAbsBMW
{
public:
CBMWVan(){ msModel = "7系列车型商务车"; };
~CBMWVan(){}; string msGetModel(){ return msModel; }; private:
string msModel;
};
2.2.4 宝马SUV
class CBMWSuv : public CAbsBMW
{
public:
CBMWSuv(){ msModel = "X系列车型SUV"; };
~CBMWSuv(){}; string msGetModel(){ return msModel; }; private:
string msModel;
};
2.2.5 奔驰抽象类
奔驰车与宝马车类似,都已经有清晰品牌定义,但是型号还没有确认,也是一个抽象的产品类。
class CAbsBenz :public CICar
{
public:
CAbsBenz() { msBand = "奔驰汽车"; }
~CAbsBenz(){}; //奔驰车
string msGetBand(){ return msBand; } // 型号由具体的实现类实现
virtual string msGetModel() = ; private:
string msBand;
};
2.2.6 奔驰商务车
由于分类的标准是相同的,因此奔驰车也应该有商务车和运动车两个类型。
class CBenzVan : public CAbsBenz
{
public:
CBenzVan(){ msModel = "R系列商务车"; };
~CBenzVan(){}; string msGetModel(){ return msModel; }; private:
string msModel;
};
2.2.7 奔驰SUV
class CBenzSuv : public CAbsBenz
{
public:
CBenzSuv(){ msModel = "G系列SUV"; };
~CBenzSuv(){}; string msGetModel(){ return msModel; }; private:
string msModel;
};
2.2.8 抽象工厂
所有的产品类都已经实现了,剩下的工作就是要定义工厂类进行生产,由于产品类型多样,也导致了必须有多个工厂类来生产不同产品,首先就需要定义一个抽象工厂,声明每个工厂必须完成的职责。
抽象工厂定义了每个工厂必须生产两个类型车:SUV(运动车)和VAN(商务车),否则一个工厂就不能被实例化
class CICarFactory
{
public:
CICarFactory(){};
~CICarFactory(){};
virtual CICar * mopCreateSuv() = ;
virtual CICar * mopCreateVan() = ;
};
2.2.9 宝马车工厂
class CBMWFactory : public CICarFactory
{
public:
CBMWFactory(){};
~CBMWFactory(){}; //生产SUV
CICar * mopCreateSuv() { return new CBMWSuv; } //生产商务车
CICar * mopCreateVan() { return new CBMWVan; }
};
很简单,你要我生产宝马商务车,没问题,直接产生一个宝马商务车对象,返回给调用者,这对调用者来说根本不需要关心到底是怎么生产的,它只要找到一个宝马工厂,即可生产出自己需要的产品(汽车)。
2.2.10 奔驰车工厂
class CBenzFactory : public CICarFactory
{
public:
CBenzFactory(){};
~CBenzFactory(){}; //生产SUV
CICar * mopCreateSuv() { return new CBenzSuv; } //生产商务车
CICar * mopCreateVan() { return new CBenzVan; }
};
2.2.11 调用场景
产品和工厂都具备了,剩下的工作就是建立一个场景类模拟调用者调用
int main()
{
//要求生产一辆奔驰SUV
cout << "===要求生产一辆奔驰SUV===" << endl;
//首先找到生产奔驰车的工厂
cout << "A、 找到奔驰车工厂" << endl;
CICarFactory *op_factory = new CBenzFactory;
//开始生产奔驰SUV
cout << "B、 开始生产奔驰SUV" << endl;
CICar *op_benz_suv = op_factory->mopCreateSuv();
//生产完毕, 展示一下车辆信息
cout << "C、 生产出的汽车如下: " << endl;
cout << "汽车品牌: " << op_benz_suv->msGetBand().c_str() << endl;
cout << "汽车型号: " << op_benz_suv->msGetModel().c_str() << endl; return ;
}
2.2.12 运行结果
2.2.13 小结
对外界调用者来说,只要更换一个具备相同结构的对象,即可发生非常大的改变,如我们原本使用BenzFactory生产汽车,但是过了一段时间后,我们的系统需要生产宝马汽车,这对系统来说不需要很大的改动,只要把工厂类使用BMWFactory代替即可,立刻可以生产出宝马车,注意这里生产的是一辆完整的车,对于一个产品,只要给出产品代码(车类型)即可生产,抽象工厂模式把一辆车认为是一个完整的、不可拆分的对象。它注重完整性,一个产品一旦找到一个工厂生产,那就是固定的型号,不会出现一个宝马工厂生产奔驰车的情况。
3、造者模式生产车辆
那现在的问题是我们就想要一辆混合的车型,如奔驰的引擎,宝马的车轮,那该怎么处理呢?使用我们的建造者模式!
3.1 类图
按照建造者模式设计一个生产车辆需要把车辆进行拆分,拆分成引擎和车轮两部分,然后由建造者进行建造,想要什么车,你只要有设计图纸就成,马上可以制造一辆车出来。它注重的是对零件的装配、组合、封装,它从一个细微构件装配角度看待一个对象。我们来看生产车辆的类图。
注意看我们类图中的蓝图类Blueprint,它负责对产品建造过程定义。既然要生产产品,那必然要对产品进行一个描述,在类图中我们定义了一个接口来描述汽车。
车辆产品描述,我们定义一辆车必须有车轮和引擎
class CICar
{
public:
//汽车车轮
virtual string msGetWheel() = ;
//汽车引擎
virtual string msGetEngine() = ;
};
3.2 代码
3.2.1 具体车辆
class CCar
{
public:
CCar(const string &sEngine, const string &sWheel)
{
msEngine = sEngine;
msWheel = sWheel;
}; ~CCar(){}; //汽车车轮
string msGetWheel() { return msWheel; };
//汽车引擎
string msGetEngine() { return msEngine; }; string msGetInfo(){ return "车的轮子是: " + msWheel + "\n车的引擎是: " + msEngine; } private:
//汽车引擎
string msEngine;
//汽车车轮
string msWheel;
};
简单定义产品的属性,明确对产品的描述。我们继续来思考,因为我们的产品是比较抽象的,它没有指定引擎的型号,也没有指定车轮的牌子,那么这样的组合方式有很多,完全要靠建造者来建造,建造者说要生产一辆奔驰SUV那就得用奔驰的引擎和奔驰的车轮,该建造者对于一个具体的产品来说是绝对的权威,我们来描述一下建造者。
3.2.2 抽象建造者
class CCarBuilder
{
public:
CCarBuilder() {};
~CCarBuilder(){};
// 接收一份设计蓝图
void mvSetBlueprint(CBlueprint *opBlueprint){ mopBluprint = opBlueprint; };
CCar *mopBuildCar() { return new CCar(msBuildEngine(), msBuildWheel()); }; protected:
// 查看蓝图, 只有真正的建造者才可以查看蓝图
CBlueprint *mopGetBlueprint() { return mopBluprint; };
virtual string msBuildWheel() = ;
virtual string msBuildEngine() = ; protected:
//设计蓝图
CBlueprint *mopBluprint;
};
看到Blueprint类了,它中文的意思是“蓝图”,你要建造一辆车必须有一个设计样稿或者蓝图吧,否则怎么生产?怎么装配?该类就是一个可参考的生产样本。
3.2.3 生产蓝图
class CBlueprint
{
public:
string msGetWheel(){ return msWheel; }
void mvSetWheel(const string &sWheel){ msWheel = sWheel; } string msGetEngine(){ return msEngine; }
void mvSetEngine(const string &sEngine) { msEngine = sEngine; } private:
string msWheel;
string msEngine;
};
这和一个具体的产品Car类是一样的?错,不一样!它是一个蓝图,是一个可以参考的模板,有一个蓝图可以设计出非常多的产品,如有一个R系统的奔驰商务车设计蓝图,我们就可以生产出一系列的奔驰车。它指导我们的产品生产,而不是一个具体的产品。我们来看宝马车建造车间。
3.2.4 宝马车建造车间
class CBMWBuilder : public CCarBuilder
{
public:
CBMWBuilder(){};
~CBMWBuilder(){};
string msBuildWheel() { return mopBluprint->msGetWheel(); }
string msBuildEngine() { return mopBluprint->msGetEngine(); }
};
这是非常简单的类。只要获得一个蓝图,然后按照蓝图制造引擎和车轮即可,剩下的事情就交给抽象的建造者进行装配。奔驰车间与此类似。
3.2.5 奔驰车建造车间
class CBenzBuilder : public CCarBuilder
{
public:
CBenzBuilder(){};
~CBenzBuilder(){};
string msBuildWheel() { return mopBluprint->msGetWheel(); }
string msBuildEngine() { return mopBluprint->msGetEngine(); }
};
两个建造车间都已经完成,那现在的问题就变成了怎么让车间运作,谁来编写蓝图?谁来协调生产车间?谁来对外提供最终产品?于是导演类出场了,它不仅仅有每个车间需要的设计蓝图,还具有指导不同车间装配顺序的职责。
3.2.6 导演类
class CDirector
{
public:
CDirector()
{
mopBenzBuilder = new CBenzBuilder;
mopBMWBuilder = new CBMWBuilder;
} ~CDirector(){}; //生产奔驰SUV
CCar *mopCreateBenzSuv()
{
//制造出汽车
return mopCreateCar(mopBenzBuilder, "benz的引擎", "benz的轮胎");
} // 生产出一辆宝马商务车
CCar *mopCreateBMWVan()
{
return mopCreateCar(mopBMWBuilder, "BMW的引擎", "BMW的轮胎");
} // 生产出一个混合车型
CCar *mopCreateComplexCar()
{
return mopCreateCar(mopBMWBuilder, "BMW的引擎", "benz的轮胎");
} private:
// 生产车辆
CCar *mopCreateCar(CCarBuilder *opCarBuilder, const string &sEngine, const string &sWheel)
{
//导演怀揣蓝图
CBlueprint *op_bp = new CBlueprint();
op_bp->mvSetEngine(sEngine);
op_bp->mvSetWheel(sWheel);
opCarBuilder->mvSetBlueprint(op_bp); return opCarBuilder->mopBuildCar();
} private:
CCarBuilder *mopBenzBuilder;
CCarBuilder *mopBMWBuilder;
};
这里有一个私有方法mopCreateCar,其作用是减少导演类中的方法对蓝图的依赖,全部由该方法来完成。
3.2.7 场景调用
int main()
{
//定义出导演类
CDirector o_director; //给我一辆奔驰车SUV
cout << "===制造一辆奔驰SUV===" << endl;
CCar *op_benz_suv = o_director.mopCreateBenzSuv();
cout << op_benz_suv->msGetInfo().c_str() << endl; //给我一辆宝马商务车
cout << "===制造一辆宝马商务车===" << endl;
CCar *op_bmw_van = o_director.mopCreateBMWVan();
cout << op_bmw_van->msGetInfo().c_str() << endl; //给我一辆混合车型
cout << "===制造一辆混合车===" << endl;
CCar *op_complex_car = o_director.mopCreateComplexCar();
cout << op_complex_car->msGetInfo().c_str() << endl; return ;
}
3.2.8 调用结果
场景类只要找到导演类(也就是车间主任了)说给我制造一辆这样的宝马车,车间主任马上通晓你的意图,设计了一个蓝图,然后命令建造车间拼命加班加点建造,最终返回给你一件最新出品的产品,运行结果如下所示
3.2.9 小结
注意最后一个运行结果片段,我们可以立刻生产出一辆混合车型,只要有设计蓝图,这非常容易实现。反观我们的抽象工厂模式,它是不可能实现该功能的,因为它更关注的是整体,而不关注到底用的是奔驰引擎还是宝马引擎,而我们的建造者模式却可以很容易地实现该设计,市场信息变更了,我们就可以立刻跟进,生产出客户需要的产品。
4、总结
注意看上面的描述,我们在抽象工厂模式中使用“工厂”来描述构建者,而在建造者模式中使用“车间”来描述构建者,其实我们已经在说它们两者的区别了,抽象工厂模式就好比是一个一个的工厂,宝马车工厂生产宝马SUV和宝马VAN,奔驰车工厂生产奔驰车SUV和奔驰VAN,它是从一个更高层次去看对象的构建,具体到工厂内部还有很多的车间,如制造引擎的车间、装配引擎的车间等,但这些都是隐藏在工厂内部的细节,对外不公布。也就是对领导者来说,他只要关心一个工厂到底是生产什么产品的,不用关心具体怎么生产。而建造者模式就不同了,它是由车间组成,不同的车间完成不同的创建和装配任务,一个完整的汽车生产过程需要引擎制造车间、引擎装配车间的配合才能完成,它们配合的基础就是设计蓝图,而这个蓝图是掌握在车间主任(导演类)手中,它给建造车间什么蓝图就能生产什么产品,建造者模式更关心建造过程。虽然从外界看来一个车间还是生产车辆,但是这个车间的转型是非常快的,只要重新设计一个蓝图,即可产生不同的产品,这有赖于建造者模式的功劳。
相对来说,抽象工厂模式比建造者模式的尺度要大,它关注产品整体,而建造者模式关注构建过程,因此建造者模式可以很容易地构建出一个崭新的产品,只要导演类能够提供具体的工艺流程。也正因为如此,两者的应用场景截然不同,如果希望屏蔽对象的创建过程,只提供一个封装良好的对象,则可以选择抽象工厂方法模式。而建造者模式可以用在构件的装配方面,如通过装配不同的组件或者相同组件的不同顺序,可以产生出一个新的对象,它可以产生一个非常灵活的架构,方便地扩展和维护系统。
【设计模式】 模式PK:抽象工厂模式VS建造者模式的更多相关文章
- 【设计模式】 模式PK:工厂模式VS建造者模式
1.概述 工厂方法模式注重的是整体对象的创建方法,而建造者模式注重的是部件构建的过程,旨在通过一步一步地精确构造创建出一个复杂的对象.我们举个简单例子来说明两者的差异,如要制造一个超人,如果使用工厂方 ...
- 设计模式之工厂模式VS抽象工厂
一.工厂模式主要是为创建对象提供过渡接口,以便将创建对象的具体过程屏蔽隔离起来,达到提高灵活性的目的. 工厂模式在<Java与模式>中分为三类:1)简单工厂模式(Simple Factor ...
- 大话设计模式C++版——抽象工厂模式
前面说过,简单工厂模式是最基础的一种设计模式,那以工厂命名的设计模式就是23种设计模式中最多的一种,他们一脉相承,一步一步进化而来,这里就是其中的最后一种——抽象工厂模式(Abstract Facto ...
- Java设计模式之【工厂模式】(简单工厂模式,工厂方法模式,抽象工厂模式)
Java设计模式之[工厂模式](简单工厂模式,工厂方法模式,抽象工厂模式) 工厂模式出现的原因 在java中,创建一个对象最简单的方法就是使用new关键字.但在一些复杂的业务逻辑中,创建一个对象不只需 ...
- C#设计模式--工厂模式和抽象工厂模式
话说有三大潮牌公司一直相互PK,有一天举办了一个活动让这三大公司来一个PK,我们来看看哪家公司的上衣做出来好看穿得舒服 现在我们有一个上衣的抽象产品让三大公司来做 //抽象产品 public inte ...
- Java设计模式(3)——创建型模式之抽象工厂模式(Abstract Factory)
一.概述 抽象工厂模式是指当有多个抽象角色时,使用的一种工厂模式.抽象工厂模式可以向客户端提供一个接口,使客户端在不必指定产品的具体情况下,创建多个产品族中的产品对象. UML图: 其他的过多概念不再 ...
- 设计模式02: Abstract Factory 抽象工厂(创建型模式)
Abstract Factory 抽象工厂(创建型模式) 常见的对象创建方法: //创建一个Road对象 Road road=new Road(); new的问题: -实现依赖 ...
- java设计模式 -------- 创建模式 之 抽象工厂模式
本文是自己学习所做笔记,欢迎转载,但请注明出处:http://blog.csdn.net/jesson20121020 工厂方法模式和抽象工厂模式不好区分清楚: 工厂方法模式特点: 1. 一个抽象产品 ...
- iOS常用设计模式——工厂方法(简单工厂模式,工厂方法模式, 抽象工厂模式)
1. 简单工厂模式 如何理解简单工厂,工厂方法, 抽象工厂三种设计模式? 简单工厂方法包含:父类拥有共同基础接口,具体子类实现子类特殊功能,工厂类根据参数区分创建不同子类实例.该场景对应的UML图如下 ...
- iOS经常使用设计模式——工厂方法(简单工厂模式,工厂方法模式, 抽象工厂模式)
1. 简单工厂模式 怎样理解简单工厂,工厂方法. 抽象工厂三种设计模式? 简单工厂的生活场景.卖早点的小摊贩.他给你提供包子,馒头,地沟油烙的煎饼等,小贩是一个工厂.它生产包子,馒头,地沟油烙的煎饼. ...
随机推荐
- 将HTML页面页脚固定在页面底部(多种方法实现)
当一个HTML页面中含有较少的内容时,Web页面的footer部分随着飘上来,处在页面的半腰中间,给视觉效果带来极大的影响,接下来为大家介绍下如何将页脚固定在页面底部,感兴趣的朋友可以了解下 作为一个 ...
- 软工2017第六周团队协作——个人PSP
10.20 --10.26本周例行报告 1.PSP(personal software process )个人软件过程. 类型 任务 开始时间 结束时间 中断时间 实际用 ...
- Python学习之路3 - 字符串操作&字典
本节内容: 常用的字符串处理. 格式化输出字符串. 字符串的替换. 字符串和二进制的相互转化. 字典的操作 字符串操作 常用的字符串处理 name = 'vector' print(name.capi ...
- 团队作业week9 情景测试
一.使用人群:学生.计算机工作者.对计算机感兴趣的人 1.学生:学生是学霸系统的主要用户.学生一般会通过网络寻找与自己的课程,作业有关的信息.首先,可以通过我们的搜索功能在我们的数据库中寻找我们从网络 ...
- java通过控制鼠标实现屏幕广播
在java实现屏幕共享的小程序中提到截取屏幕时是没鼠标,为了看到教师端界面上的鼠标,可以在截取屏幕的时候,把鼠标绘制到每一张截图上去,但是由于截图的时候是一张张截取的,所以看到的鼠标难免会有点卡,之前 ...
- c# load xml 中文报错
<?xml version="1.0" encoding="GB2312"?>
- lintcode-39-恢复旋转排序数组
39-恢复旋转排序数组 给定一个旋转排序数组,在原地恢复其排序. 说明 什么是旋转数组? 比如,原始数组为[1,2,3,4], 则其旋转数组可以是[1,2,3,4], [2,3,4,1], [3,4, ...
- 利用闭包判断Dom元素和滚动条的方向
本文收集整理自网上. 一,判断滚动条的方向,利用闭包首先保存滚动条的位置,然后当滚动时候不断更新滚动初始值,然后通过差指判断方向 function scroll(fn) { //利用闭包判断滚动条滚动 ...
- Delphi SQL语句字符串拼接
单引号必须成对出现,最外层的单引号表示其内部符号为字符:除最外层以外的单引号,每两个单引号代表一个'字符.加号:+用于字符串之间的连接.字符串常量用四个单引号,例如 ' select * from T ...
- HttpServletRequestWrapper 是HttpServletRequest的包装类 ·关系相当于 int 与integer的关系
HttpServletRequestWrapper 是HttpServletRequest的包装类 ·关系相当于 int 与integer的关系