一、简介
工厂模式主要是为创建对象提供了接口。工厂模式按照《Java与模式》中的提法分为三类:
这三种模式从上到下逐步抽象,并且更具一般性。还有一种分类法,就是将简单工厂模式看为工厂方法模式的一种特例,两个归为一类。下面是使用工厂模式的两种情况:
1.在编码时不能预见需要创建哪种类的实例。
2.系统不应依赖于产品类实例如何被创建、组合和表达的细节 。
二、简单工厂模式(Simple Factory)
顾名思义,这个模式本身很简单,而且使用在业务较简单的情况下。
它由三种角色组成(关系见下面的类图):
1、工厂类角色:这是本模式的核心,含有一定的商业逻辑和判断逻辑。在java中它往往由一个具体类实现。
2、抽象产品角色:它一般是具体产品继承的父类或者实现的接口。在java中由接口或者抽象类来实现。
3、具体产品角色:工厂类所创建的对象就是此角色的实例。在java中由一个具体类实现。
类图如下:
使用简单工厂模式来实现一个实例:
用面向对象语言实现一个计算机控制台程序:(以java为例)
源代码如下:
Operation.java
package com.caculate;
public class Operation {
protected double numberA=0;
protected double numberB=0;
public double getNumberA() {
return numberA;
}
public void setNumberA(double numberA) {
this.numberA = numberA;
}
public double getNumberB() {
return numberB;
}
public void setNumberB(double numberB) {
this.numberB = numberB;
}
public double getResult(){
double result=0;
return result;
}
}
OperationAdd.java(加法类)
package com.caculate;
/** *加法类 */
public class OperationAdd extends Operation{
public double getResult() {
double result=0;
result=numberA + numberB;
return result;
}
}
OperationSub.java(减法类)
package com.caculate;
/** *减法类 */
public class OperationSub extends Operation{
public double getResult() {
double result=0;
result=numberA - numberB;
return result;
}
}
OperationMul.java(乘法类)
package com.caculate;
/** *乘法类 */
public class OperationMul extends Operation{
public double getResult() {
double result = 0;
result=numberA * numberB;
return result;
}
}
OperationDiv.java(除法类)
package com.caculate;
/** *除法类 */
public class OperationDiv extends Operation{
public double getReault() {
double result=0;
result=numberA/numberB;
return result;
}
}
OperationFactory.java(工厂类,用来实例化需要用到的运算类对象)
package com.caculate;
/** *构建一个工厂类来实例化对象 */
public class OperationFactory {
/** * @param operate 传入一个操作符 * @return 返回一个对应该操作数的Operation类对象 */
public static Operation createOperate(String operate){
Operation oper=null;
int n=0;
if (operate.equals("+")) {
n=1;
}
if (operate.equals("-")) {
n=2;
}
if (operate.equals("*")) {
n=3;
}
if (operate.equals("、")) {
n=4;
}
/**注意: * JRE1.7以下的版本中,switch的判断条件必须是一个int型值,也可以是byte、short、char型值; * 在1.7增加新特性能传string类型 */
switch (n) {
case 1:
oper=new OperationAdd();//如果传入的操作符是+,则实例化一个加法类对象
break;
case 2:
oper=new OperationSub();//如果传入的操作符是-,则实例化一个减法类对象
break;
case 3:
oper=new OperationMul();//如果传入的操作符是*,则实例化一个乘法类对象
break;
case 4:
oper=new OperationDiv();//如果传入的操作符是/,则实例化一个除法类对象
break;
}
return oper;
}
}
Test.java(测试类)
package com.caculate;
public class Test {
public static void main(String[] args) {
Operation oper=OperationFactory.createOperate("+");
oper.numberA=1;
oper.numberB=4;
double result=oper.getResult();
System.out.println(result);
}
}
运行结果: 5.0
总结:
1、当我们需要修改相应的运算时,只需要修改相应的实现类(加减乘除等的实现);当需要增加其他的复杂运算(比如:平方根,立方根等)时,只需要实现相应的运算(既增加运算类的子类),然后增加运算类工厂中的switch分支即可;在使用的时候,只需要输入运算符号,工厂类就能实例化出合适的对象,通过多态,返回父类的方式来实现计算器的结果,而使用者无需了解具体的实现过程,充分契合了面向对象继承、封装和多态的特性。
2、使用了简单工厂模式后,我们的程序更加符合现实中的情况;而且客户端免除了直接创建产品对象的责任,而仅仅负责”消费”产品。
我们举的例子是最简单的情况,而在实际应用中,很可能产品是一个多层次的树状结构。由于简单工厂模式中只有一个工厂类来对应这些产品,所以实现起来会很麻烦。所以简单工厂模式适用于业务较简单的情况下。而对于复杂的业务环境可能不太适应。这就应该由工厂方法模式来出场了。
三、工厂方法模式(Factory Method)
简单工厂模式最大的优点在于工厂类中包含了必要的逻辑判断,根据客户端的选择条件动态实例化相关的类,对于客户端来说,去除了与具体产品的依赖。但是,如果我们要加一个“求M数的N次方的功能”,我们是一定需要给运算工厂类的方法里加“case”分支条件的,这就修改了原来的类,就等于我们不但对扩展开放了,也对修改开放了,违背了开放-封闭原则。
于是,工厂方法模式就来了。
工厂方法模式(Factory Method)定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。
工厂方法模式结构图如下:
还是以上面的计算器为例子,既然这个工厂类与分支耦合,那么我们就对它下手,根据依赖倒转原则,我们把工厂类抽象出一个接口,这个接口只有一个方法,就是创建抽象产品的工厂方法。然后,所有的要生产具体类的工厂,就去实现这个接口。这样,一个简单工厂模式的工厂类,变成了一个工厂抽象接口和多个具体生成对象的工厂,于是我们增加“求M数的N次方”的功能时,就不需要更改原有的工厂类,只需要增加此功能的运算类和相应的工厂类就可以了。
这样,整个工厂和产品体系都没有修改的变化而只是扩展的变化,这就完全符合开放-封闭原则的精神,同时又保持了封装对象创建过程的优点,降低了客户端与产品对象的耦合。
另外,工厂方法模式实现时,客户端需要决定实例化哪一个工厂来实现运算类,选择判断的问题还是存在的,也就是说,工厂方法把简单工厂方法的内部逻辑判断移到了客户端代码来进行。如果想要增加功能,本来是改工厂类,而现在是修改客户端。
工厂方法模式是简单工厂模式的进一步抽象和推广。由于使用了多态性,工厂方法模式保持了简单工厂模式的优点,而且克服了它的缺点。但还存在不足,就是由于每增加一个产品,就需要增加一个产品工厂的类,增加了额外的开发量。这样,又有一个问题抛出来了,还有没有可以避免修改客户端的实现方法呢?
四、抽象工厂模式(Abstract Factory)
以一个简单的数据库访问程序来系统学习抽象工厂模式。
首先,我们结合上面刚学到的工厂方法模式来实现这个数据库访问程序,类图如下:
客户端代码:
public static void main(String[] args) {
User user = new User();
IFactory factory = new SqlServerFactory();//数据库为sqlserver时
//IFactory factory = new AccessFactory();//数据库为access时
IUser iu = factory.createUser();
iu.insert(user);//插入用户
iu.getUser(1);//得到ID为1的用户
Console.Read();
}
如果要换数据库,我们只需要把new SqlServerFactory()改为new AccessFactory(),此时由于多态的关系,使得声明IUser接口的对象iu事先根本不知道是在访问哪个数据库,却可以在运行时很好的完成工作,这就是所谓的业务逻辑与数据库操作解耦。
如果我们在数据库中增加一个Department(部门表),就涉及到解决这种涉及到多个产品系列的问题,那么,抽象工厂模式就可以粉墨登场了。
(IFactory接口下也有一个CreateDepartment()方法,画图时漏掉了)
那么,我们需要在原程序的基础上做如下改动:
1、增加一个Department类;
2、增加一个IDepartment接口,用于客户端访问,解除与具体数据库访问的耦合;
3、增加一个SqlserverDepartment类,用于访问SQL Server的Department;
4、增加一个AccessDepartment类,用于访问Access的Department;
5、IFactory接口是定义一个创建访问Department表对象的抽象的工厂接口,增加CreateDepartment()方法,相应的SqlServerFactory和AccessFactory类都要增加该方法;
客户端代码如下:
public static void main(String[] args) {
User user = new User();
IFactory factory = new SqlServerFactory();//数据库为sqlserver时
//IFactory factory = new AccessFactory();//数据库为access时
IUser iu = factory.createUser();
iu.insert(user);//插入用户
iu.getUser(1);//得到ID为1的用户
IDepartment id = factory.createDepartment();
id.insert(dept);//插入部门
id.getDept(1);//得到ID为1的部门
Console.Read();
}
抽象工厂模式(Abstract Method),提供一个创建一系列相关或者相互依赖对象的接口,而无需指定它们具体的类。
抽象工厂模式的优点:是易于交换产品系列,由于具体工厂类在一个应用中只需要在初始化的时候出现一次,这就使得改变一个应用的具体工厂变得非常容易,它只需要改变具体工厂即可使用不同的产品配置。我们的设计不能去防止需求的更改,那么我们的理想便是让改动变的最小,现在如果你要更改数据库访问,我们只需要更改具体工厂就可以做到。第二大好处是它让具体的创建实例过程与客户端分离,客户端是通过它们的抽象接口操纵实例,产品的具体类名也被具体工厂的实现分离,不会出现在客户代码中。
抽象工厂模式缺点:虽然抽象工厂模式可以很方便的切换两个数据库访问的代码,但是如果你的需求来自增加功能,比如我们现在要增加项目表Project,就至少需要增加三个类,IProject、SqlserverProject、AccessProject,还需要更改IFactory、SqlserverFactory和AccessFactory才可以完全实现。而且,我们的客户端程序类显然不会只是一个,有很多地方都在使用IUser或者IDepartment,而这样的设计,我们在更改数据库的时候,所有的地方都要发生改动,这么大批量的改动显然是非常糟糕的。
使用简单工厂模式来改进抽象工厂模式