转载请注明出处:http://blog.csdn.net/h_zhang/article/details/51245465
前面我写了一篇关于简单工厂模式的文章,对于简单工厂模式还不清楚的同学建议先去阅读:java设计模式之简单工厂模式 ;本篇文章将讲解抽象工厂模式,并和简单工厂模式的异同点做了对比。
先回顾一下简单工厂模式: 首先,组件的之间的调用应该是面向接口的,这样做可以隔离变化。其次,虽然高层组件通过底层组件的接口来调用底层组件的功能,但是底层组件的接口如何实例化呢?为了避免组件之间耦合度太高,高层组件一般不直接实例化底层组件的接口对象,而是通过简单工厂来获得底层组件的接口对象。这样一来,如果底层组件的具体实现发生变化,就不用变动高层组件的代码,因为这个变化会被简单工厂吸收和屏蔽掉,从而实现组件之间的解耦。
下面就进入本文的主题:抽象工厂模式。
场景需求
举个生活中常见的场景:组装电脑。装机工程师组装一台电脑需要CPU,主板,内存,硬盘,显卡等等。为了演示方便就以CPU、主板为例,其他就不考虑了。现在CPU可能有多种品牌,比如Intel的CPU,AMD的CPU;主板也可能有多种,比如技嘉的主板,华硕的主板。对于装机工程师来说,他只要拿到CPU和主板进行组装即可,他不需要知道是CPU是哪家的CPU,主板是哪家的主板。而CPU和主板的选择是由客户端来决定。
对上面问题进行抽象:装机工程师就是高层组件;CPU和主板就是底层组件。装机工程师需要底层组件的支持才能完成装机工作。
对于装机工程师而言,只知道CPU和主板接口,而不知道具体实现,很明显可以用简单工厂实现啊。客户端告诉装机工程师自己的选择,然后通过相应的工厂获取实例对象。
使用简单工厂实现
首先,我们准备好底层组件。
面向接口编程是前提,先来看看CPU和主板的接口。
CPU的接口定义:
/**
* CPU接口
*/
public interface CPU
{
//示意方法, cpu具有运算功能
public void calculate();
}
主板接口定义:
/**
* 主板接口
*/
public interface Mainboard
{
//示意方法,主板具有安装CPU功能
public void installCPU();
}
再来看看CPU和主板的实现。
底层组件负责实现这些接口,下面来看看具体CPU的实现代码。
IntelCPU的具体代码实现:
/**
* Intel CPU的实现
*/
public class IntelCPU implements CPU
{
//CPU的针脚数目
private int pins;
//构造方法,传入针脚数目
public IntelCPU(int pins)
{
this.pins = pins;
}
@Override
public void calculate()
{
System.out.println("Intel CPU, pins="+pins);
}
}
AMD CPU的具体代码实现:
/**
* AMD CPU的实现
*/
public class AmdCPU implements CPU
{
//CPU的针脚数目
private int pins;
public AmdCPU(int pins)
{
this.pins = pins;
}
@Override
public void calculate()
{
System.out.println("AMD CPU, pins="+pins);
}
}
下面来看看具体主板的实现。
技嘉主板的代码实现:
/**
* 技嘉的主板
*/
public class GAMainboard implements Mainboard
{
//CPU插槽的孔数
private int cpuHoles;
public GAMainboard(int cpuHoles)
{
this.cpuHoles = cpuHoles;
}
@Override
public void installCPU()
{
System.out.println("GA Mainboard, cpuHoles="+cpuHoles);
}
}
华硕主板的代码实现:
/**
* 华硕主板
*/
public class ASUSMainboard implements Mainboard
{
//CPU插槽的孔数
private int cpuHoles;
public ASUSMainboard(int cpuHoles)
{
this.cpuHoles = cpuHoles;
}
@Override
public void installCPU()
{
System.out.println("ASUS Mainboard, cpuHoles="+cpuHoles);
}
}
OK,底层组件准备完毕。下面我们搞出两个简单工厂,分别负责创建CPU和主板的接口对象。
创建CPU工厂的示例代码:
/**
* 创建CPU的简单工厂
*/
public class CPUFactory
{
/**
* 创建CPU接口对象的方法
* @param type 选择CPU类型参数
* @return CPU接口对象方法
*/
public static CPU createCPU(int type)
{
CPU cpu = null;
if(type == 1)
{
cpu = new IntelCPU(1156);
}else if(type == 2)
{
cpu = new AmdCPU(939);
}
return cpu;
}
}
创建主板的工厂示例代码:
/**
* 创建主板的简单工厂
*/
public class MainboardFactory
{
/**
* 创建主板接口对象方法
* @param type 选择主板类型参数
* @return 主板接口对象
*/
public static Mainboard createMainboard(int type)
{
Mainboard mainboard = null;
if(type == 1){
mainboard = new GAMainboard(1156);
}else if(type == 2){
mainboard = new ASUSMainboard(939);
}
return mainboard;
}
}
代码相当简单,没什么好说的。
下面我们看看装机工程师的示例代码:
/**
* 装机工程师类
*/
public class ComputerEngineer
{
//装机需要的CPU
private CPU cpu;
//装机需要的主板
private Mainboard mainboard;
/**
* 装机过程
* @param cpuType 客户所选CPU类型
* @param mainboardType 客户所选主板类型
*/
public void makeComputer(int cpuType, int mainboardType)
{
//1.准备好装机所需配件
this.cpu = CPUFactory.createCPU(cpuType);
this.mainboard = MainboardFactory.createMainboard(mainboardType);
//测试配件是否好用
this.cpu.calculate();
this.mainboard.installCPU();
//2.有了配件之后,组装机器
//3.组装完毕,测试,交付用户
}
}
OK, 类ComputerEngineer就是高层组件,其内部所维持的都是底层组件的接口(cpu, mainboard),也就是说装机工程师并不知道cpu和mainboard到底谁来实现以及怎么实现的。装机工程师向客户端提供makeComputer()方法来完成装机过程。装机第1步通过简单工厂获得底层配件实例对象。
下面我们来看客户端代码:
public class Client
{
public static void main(String[] args)
{
//创建装机工程师实例
ComputerEngineer computerEngineer = new ComputerEngineer();
//告诉装机工程师自己的配件,让装机工程师组装电脑
computerEngineer.makeComputer(1, 1);
}
}
运行结果如下:
Intel CPU, pins=1156
GA Mainboard, cpuHoles=1156
OK,利用简单工厂就实现好了,通过简单工厂获取CPU和主板对象,然后就可以组装电脑了,貌似非常完美。然而有一个问题没有解决。。。
上面的实现,利用简单工厂解决了装机工程师和CPU、主板之间的解耦问题:对于装机工程师而言只知道CPU和主板的接口,而不知道具体实现。CPU和主板的改变不会影响到装机工程师的代码,因为CPU和主板的改变会被简单工厂吸收和屏蔽。但是还有一个问题没有解决,那就是CPU对象和主板对象之间是有关系的,是需要相互匹配的。而上面的实现中并没有维护这种依赖关系。CPU和主板的型号,客户端可以随意选择,这当然是有问题的。
比如,如果客户端在调用makeComputer()方法时传入的参数是(1,2),运行结果如下:
Intel CPU, pins=1156
GA Mainboard, cpuHoles=939
这样就出现问题:因为1156个针脚的CPU是无法安装到939个针孔插槽的主板上。要解决这个问题,可以利用抽象工厂模式。
抽象工厂模式
先看一下抽象工厂模式的定义:
提供创建一系列互相依赖对象的接口,而无需指定他们具体的类。
分析上面的问题,其实有两个问题点。一个是:只知道一系列对象的接口而不知道其具体实现,或者不知道具体使用哪一个实现。另一个是:这一系列对象是相互依赖的,要约束他们之间的关系。不能孤立的创建它们。
我们定义一个抽象工厂,在里面抽象的定义出创建CPU和主板的方法。抽象工厂对外提供了一个统一的外观,由抽象工厂的子类创建出来的一系列对象肯定是能够相互匹配的。
也就是说,抽象工厂不再单独创建某一个产品对象,而是创建一系列产品对象(产品簇)。这也是抽象工厂模式和简单工厂模式最大的区别。
还有一点需要说明:大家不要被抽象工厂的名称所误导,以为是抽象类。实际上抽象工厂大多都是以接口的形式实现的。
还是通过代码说明比较直观。下面我们利用抽象工厂重写上面示例。
需要注意一下两点:
1. 底层组件对象(CPU, Mainboard)还是上面的代码,不用改变;
2. 创建CPU的简单工厂和主板的简单工厂不再需要,直接删除即可;
OK,下面先加入抽象工厂的定义:
/**
* 抽象工厂接口,定义创建一系列抽象产品
*/
public interface AbstractFactory
{
/**
* 创建CPU对象
* @return CPU的对象
*/
public CPU createCPU();
/**
* 创建主板对象
* @return 主板对象
*/
public Mainboard createMainboard();
}
下面看看具体的装机方案,也就是为抽象工厂的实现子类。
装机方案一的实现。示例代码如下:
/**
* 装机方案一:Intel的CPU + 技嘉的主板
*
* 这里创建的CPU和主板对象是对应,能匹配上的
*/
public class ComputerSchema1 implements AbstractFactory
{
@Override
public CPU createCPU()
{
return new IntelCPU(1156);
}
@Override
public Mainboard createMainboard()
{
return new GAMainboard(1156);
}
}
装机方案二的实现。示例代码如下:
/**
* 装机方案一:AMD的CPU + 华硕的主板
*
* 同样,这里创建的CPU和主板对象是对应,能匹配上的
*/
public class ComputerSchema2 implements AbstractFactory
{
@Override
public CPU createCPU()
{
return new AmdCPU(939);
}
@Override
public Mainboard createMainboard()
{
return new ASUSMainboard(939);
}
}
下面,装机工程师的代码要稍微改变一下。
/**
* 装机工程师类
*/
public class ComputerEngineer
{
//装机需要的CPU
private CPU cpu;
//装机需要的主板
private Mainboard mainboard;
/**
* 组装电脑过程
* @param schema 客户端选择装机方案
*/
public void makeComputer(AbstractFactory schema)
{
//1.准备好装机所需配件
this.cpu = schema.createCPU();
this.mainboard = schema.createMainboard();
//测试配件是否好用
this.cpu.calculate();
this.mainboard.installCPU();
//2.有了配件之后,组装机器
//3.组装完毕,测试,交付用户
}
}
可以看到,makeComputer()方法不再接收具体的cpuType和mainboardType参数,而是接收一个抽象工厂对象。这个抽象工厂对象负责实例化哪一个CPU和哪一个主板。也就是说,客户端不再通过指定具体cpu和主板来组装电脑,而是选择一个抽象工厂的实现类去组装电脑,每一个抽象工厂的实现类就代表一种装机方案,并且由抽象工厂确定的CPU和主板的型号肯定是相互匹配的。
那么下面就来看看客户端代码:
public class Client
{
public static void main(String[] args)
{
//创建装机工程师实例
ComputerEngineer computerEngineer = new ComputerEngineer();
//选择一个装机方法
AbstractFactory schema = new ComputerSchema1();
//告诉装机工程师自己的配件,让装机工程师组装电脑
computerEngineer.makeComputer(schema);
}
}
OK!回顾上面分析两个问题点,利用抽象工厂是不是都解决了呢?
答案是肯定的。第一、装机工程师是只知道电脑配件的接口,而不知道其具体实现以及由谁实现,从而实现了组件之间的解耦。第二、利用抽象工厂给出的装机方案,每个配件之间肯定是相互匹配的。因为配件的选择由抽象工厂的实现类去控制,每一个抽象工厂对象就会创建一系列产品(产品簇),就代表一种装机方案。用户直接选择使用哪一个抽象工厂的实现类即可。
总结
抽象工厂的优点:
1. 分离接口和实现,使得客户端只面向接口编程即可。当然这个特点,简单工厂也有。
2. 切换产品簇变得容易;比如上面的例子的ComputerSchema1代表装机方案:Intel的CPU+技嘉的主板;如果要切换成ComputerSchema2就变成新的方案:AMD的CPU+华硕的主板。是不是很方便呢。。。
抽象工厂和简单工厂对比:
工厂嘛!造东西的。在java里面就是创造接口对象的。
不同的地方就是:简单工厂主要用来创建一个接口对象(当然可以创建多个接口对象,但是这些对象之间是没有约束关系的,不相互依赖的)。而抽象工厂则是用来创建一系列相互依赖的接口对象(就比如上面装机例子中的CPU和主板)。
简单工厂可以看做是抽象工厂的特例,当抽象工厂中只创建一个接口对象,那么抽象工厂就退化成简单工厂了。
OK,那么本篇文章到这就结束了。关于抽象工厂模式就说这么多,谢谢大家观看~~~