简单工厂模式又叫静态工厂方法模式(Static Factory Method Pattern),是通过专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。
一个简单的实例:要求实现一个计算机控制台程序,要求输入数的运算结果。最原始的解决方法如下:
/**
* @Description:这里使用的是最基本的实现,并没有体现出面向对象的编程思想,代码的扩展性差,甚至连除数可能为0的情况也没有考虑
*/
public static void main(String[] args) {
scanner = new Scanner(System.in);
System.out.print("请输入第一个数字:");
int firstNum = scanner.nextInt();
System.out.print("请输入第二个数字:");
int secondNum = scanner.nextInt();
System.out.print("请输入运算符:");
String operation = scanner.next();
if(operation.equals("+")) {
System.out.println("result:" + (firstNum + secondNum));
} else if(operation.equals("-")) {
System.out.println("result:" + (firstNum - secondNum));
} else if(operation.equals("*")) {
System.out.println("result:" + (firstNum * secondNum));
} else if(operation.equals("/")){
System.out.println("result:" + (firstNum / secondNum));
}
}
上面的写法实现虽然简单,但是却没有面向对象的特性,代码拓展性差,甚至没有考虑除数可能为0的特殊情况。
在面向对象编程语言中,一切都是对象,所以上面运算符号也应当作对象来处理。因此我们首先建立一个运算接口,所有其他的运算都封装成类,并实现该运算接口。
/**
* @Description: 定义一个运算接口,将所有的运算符号都封装成类,并实现本接口
* @author: zxt
* @time: 2018年7月6日 上午10:24:13
*/
public interface Operation {
public double getResult(double firstNum, double secondNum);
}
public class AddOperation implements Operation {
@Override
public double getResult(double firstNum, double secondNum) {
return firstNum + secondNum;
}
}
public class SubOperation implements Operation {
@Override
public double getResult(double firstNum, double secondNum) {
return firstNum - secondNum;
}
}
public class MulOperation implements Operation {
@Override
public double getResult(double firstNum, double secondNum) {
return firstNum * secondNum;
}
}
public class DivOperation implements Operation {
@Override
public double getResult(double firstNum, double secondNum) {
if(secondNum == 0) {
try {
throw new Exception("除数不能为0!");
} catch (Exception e) {
e.printStackTrace();
}
}
return firstNum / secondNum;
}
}
现在的问题的是,如何根据不同的情况创建不同的对象,这里就可以使用简单工厂模式来实现了,客户端只需要提供运算符,工厂类会判断并生成相应的运算类:
/**
* @Description: 简单工厂模式:通过一个工厂类,根据情况创建不同的对象
* @author: zxt
* @time: 2018年7月6日 上午10:50:15
*/
public class OperationFactory {
/**
* @Description:根据运算符得到具体的运算类
* @param operationStr
*/
public static Operation getOperation(String operationStr) {
Operation result = null;
switch(operationStr) {
case "+":
result = new AddOperation();
break;
case "-":
result = new SubOperation();
break;
case "*":
result = new MulOperation();
break;
case "/":
result = new DivOperation();
break;
}
return result;
}
}
// 客户端调用
Operation oper = OperationFactory.getOperation(operation);
double result = oper.getResult(firstNum, secondNum);
System.out.println(result);
简单工厂将对象的创建过程进行了封装,用户不需要知道具体的创建过程,只需要调用工厂类获取对象即可。
这种简单工厂的写法是通过switch-case来判断对象创建过程的。在实际使用过程中,违背了开放-关闭原则(例如,当需要扩展一个新的运算符之后,简单工厂创建的对象也必须多一种,这就需要修改原来的代码了,违背了对修改关闭的原则),当然有些情况下可以通过反射调用来弥补这种不足。
简单工厂模式的最大优点在于工厂类中包含了必要的逻辑判断,根据客户端的选择条件动态实例化相关的类,对于客户端来说,去除了与具体产品的依赖。但是每扩展一个类时,都需要改变工厂类里的方法,这就违背了开放-封闭原则。于是工厂方法模式来了:
工厂方法模式(Factory Method),定义一个用于创建对象的接口,让子类决定实例化哪一个类,工厂方法使一个类的实例化延迟到其子类。 继续上一个计算器的例子,简单工厂模式由工厂类直接生成相应的运算类对象,判断的逻辑在工厂类中,而工厂方法模式的实现则是定义一个工厂接口,然后每个运算类都对应一个工厂类来创建,然后在客户端判断使用哪个工厂类来创建运算类。
/**
* @Description: 工厂的接口
* @author: zxt
* @time: 2019年2月21日 下午2:49:43
*/
public interface IFactory {
public Operation createOperation();
}
/**
* @Description: 加法类工厂
*/
public class AddFactory implements IFactory {
@Override
public AddOperation createOperation() {
return new AddOperation();
}
}
/**
* @Description: 减法类工厂
*/
public class SubFactory implements IFactory {
@Override
public SubOperation createOperation() {
return new SubOperation();
}
}
/**
* @Description: 乘法类工厂
*/
public class MulFactory implements IFactory {
@Override
public MulOperation createOperation() {
return new MulOperation();
}
}
/**
* @Description: 除法类工厂
*/
public class DivFactory implements IFactory {
@Override
public DivOperation createOperation() {
return new DivOperation();
}
}
工厂方法模式实现时,客户端需要决定实例化哪一个工厂来实现运算类,选择判断的问题还是存在的,也就是说,工厂方法把简单工厂的内部逻辑判断移到了客户端代码来进行。你想要加功能,本来是改工厂类的,而现在是修改客户端。
/**
* @Description: 实现一个简单的计算器功能,使用工厂方法模式
* @author: zxt
* @time: 2018年7月6日 上午10:11:50
*/
public class Computer {
private static Scanner scanner;
public static void main(String[] args) {
scanner = new Scanner(System.in);
System.out.print("请输入第一个数字:");
int firstNum = scanner.nextInt();
System.out.print("请输入第二个数字:");
int secondNum = scanner.nextInt();
System.out.print("请输入运算符:");
String operation = scanner.next();
IFactory operFactory = null;
if(operation.equals("+")) {
operFactory = new AddFactory();
} else if(operation.equals("-")) {
operFactory = new SubFactory();
} else if(operation.equals("*")) {
operFactory = new MulFactory();
} else if(operation.equals("/")){
operFactory = new DivFactory();
}
Operation oper = operFactory.createOperation();
double result = oper.getResult(firstNum, secondNum);
System.out.println("result = " + result);
}
}
增加新功能时,工厂方法模式比简单工厂模式修改的代码量更小,工厂方法克服了简单工厂违背开放封闭原则的缺点,又保持了封装对象创建过程的优点。但是工厂方法的缺点就是每加一个产品,就需要加一个产品工厂的类,增加了额外的开发量。当然这两种模式都还不是最佳的做法。
抽象工厂模式是所有形态的工厂模式中最为抽象和最具一般性的一种形态。抽象工厂模式是指当有多个抽象角色时,使用的一种工厂模式。抽象工厂模式可以向客户端提供一个接口,使客户端在不必指定产品的具体的情况下,创建多个产品族中的产品对象。根据里氏替换原则,任何接受父类型的地方,都应当能够接受子类型。因此,实际上系统所需要的,仅仅是类型与这些抽象产品角色相同的一些实例,而不是这些抽象产品的实例。换言之,也就是这些抽象产品的具体子类的实例。工厂类负责创建抽象产品的具体子类的实例。
抽象工厂模式(Abstract Factory):提供一个创建一系列相关或相互依赖对象的接口,而无需指定他们具体的类。
实例场景:对数据库(各种不同的数据库)中的表进行修改,此时,使用工厂模式结构图如下:
1、User表的定义:
public class User {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
2、定义一个对User表进行操作的接口:
/**
* @Description: 对User类操作的接口
* @author: zxt
* @time: 2019年2月24日 下午7:00:20
*/
public interface IUser {
void insert(User user);
User getUser(int id);
}
3、实现Sql Server数据库对User表的操作:
/**
* @Description: SQL Server数据库中对User表的操作
* @author: zxt
* @time: 2019年2月24日 下午7:04:39
*/
public class SqlServerUser implements IUser {
@Override
public void insert(User user) {
System.out.println("在 SQL Server 中给 User 表增加一条记录!");
}
@Override
public User getUser(int id) {
System.out.println("在 SQL Server 中根据ID得到 User 表的一条记录!");
return null;
}
}
实现Oracle数据库对User表的操作:
/**
* @Description: Oracle数据库中对User表的操作
* @author: zxt
* @time: 2019年2月24日 下午7:05:07
*/
public class OracleUser implements IUser {
@Override
public void insert(User user) {
System.out.println("在 Oracle 中给 User 表增加一条记录!");
}
@Override
public User getUser(int id) {
System.out.println("在 Oracle 中根据ID得到 User 表的一条记录!");
return null;
}
}
4、定义一个抽象工厂接口,用于生成对User表的操作的对象:
/**
* @Description: 得到对User表操作的IUser对象的抽象工厂接口
* @author: zxt
* @time: 2019年2月24日 下午7:06:37
*/
public interface IFactory {
public IUser createUser();
}
5、SQLServerFactory工厂用于生成操作Sql Server数据库的SqlServerUser对象:
public class SQLServerFactory implements IFactory {
@Override
public IUser createUser() {
return new SqlServerUser();
}
}
OracleFactory工厂用于生成操作Oracle数据库的OracleUser对象:
public class OracleFactory implements IFactory {
@Override
public IUser createUser() {
return new OracleUser();
}
}
6、客户端的使用:
public class FactoryMethodTest {
public static void main(String[] args) {
User user = new User();
// 若要改成Oracle数据库,只需要将这句改成OracleFactory即可
IFactory ifactory = new SQLServerFactory();
IUser iu = ifactory.createUser();
iu.insert(user);
iu.getUser(1);
}
}
到此为止,工厂模式都可以很好的解决,由于多态的关系,IFactory在声明对象之前都不知道在访问哪个数据库,却可以在运行时很好的完成任务,这就是业务逻辑与数据访问的解耦。
但是,当数据库中不止一个表的时候该怎么解决问题呢,此时就可以引入抽象工厂模式了,结构图如下:
例如增加了部门表Department:
public class Department {
private int id;
private String deptName;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getDeptName() {
return deptName;
}
public void setDeptName(String deptName) {
this.deptName = deptName;
}
}
则增加相应的对Department表操作的接口:
public interface IDepartment {
public void insert(Department department);
public Department getDepartment(int id);
}
实现Sql Server数据库对Department表的操作:
/**
* @Description: SQL Server数据库中对Department表的操作
* @author: zxt
* @time: 2019年2月24日 下午7:04:39
*/
public class SqlServerDepartment implements IDepartment {
@Override
public void insert(Department user) {
System.out.println("在 SQL Server 中给 Department 表增加一条记录!");
}
@Override
public Department getDepartment(int id) {
System.out.println("在 SQL Server 中根据ID得到 Department 表的一条记录!");
return null;
}
}
实现Oracle数据库对Department表的操作:
/**
* @Description: Oracle数据库中对Department表的操作
* @author: zxt
* @time: 2019年2月24日 下午7:05:07
*/
public class OracleDepartment implements IDepartment {
@Override
public void insert(Department user) {
System.out.println("在 Oracle 中给 Department 表增加一条记录!");
}
@Override
public Department getDepartment(int id) {
System.out.println("在 Oracle 中根据ID得到 Department 表的一条记录!");
return null;
}
}
IFactory抽象工厂中增加生成对Department表操作的对象:
/**
* @Description: 得到对User表操作的IUser对象的抽象工厂接口
* @author: zxt
* @time: 2019年2月24日 下午7:06:37
*/
public interface IFactory {
public IUser createUser();
public IDepartment createDepartment();
}
SQLServerFactory工厂增加生成操作Sql Server数据库的SqlServerDepartment对象:
public class SQLServerFactory implements IFactory {
@Override
public IUser createUser() {
return new SqlServerUser();
}
@Override
public IDepartment createDepartment() {
return new SqlServerDepartment();
}
}
OracleFactory工厂增加生成操作Oracle数据库的OracleDepartment对象:
public class OracleFactory implements IFactory {
@Override
public IUser createUser() {
return new OracleUser();
}
@Override
public IDepartment createDepartment() {
return new OracleDepartment();
}
}
客户端的使用:
public class AbstractFactoryTest {
public static void main(String[] args) {
User user = new User();
Department department = new Department();
// 若要改成SQL Server数据库,只需要将这句改成SqlServerFactory即可
IFactory ifactory = new OracleFactory();
IUser iu = ifactory.createUser();
iu.insert(user);
iu.getUser(1);
IDepartment id = ifactory.createDepartment();
id.insert(department);
id.getDepartment(1);
}
}
所以抽象工厂与工厂方法模式的区别在于:抽象工厂是可以生产多个产品的,例如OracleFactory 里可以生产 OracleUser以及 OracleDepartment两个产品,而这两个产品又是属于一个系列的,因为它们都是属于Oracle数据库的表。而工厂方法模式则只能生产一个产品,例如之前的 OracleFactory里就只可以生产一个 OracleUser产品。
抽象工厂模式的优缺点:
优点:
1、抽象工厂模式最大的好处是易于交换产品系列, 由于具体工厂类,例如 IFactory factory = new OracleFactory(); 在一个应用中只需要在初始化的时候出现一次,这就使得改变一个应用的具体工厂变得非常容易,它只需要改变具体工厂即可使用不同的产品配置。不管是任何人的设计都无法去完全防止需求的更改,或者项目的维护,那么我们的理想便是让改动变得最小、最容易。
2、抽象工厂模式的另一个好处就是它让具体的创建实例过程与客户端分离,客户端是通过它们的抽象接口操作实例,产品实现类的具体类名也被具体的工厂实现类分离,不会出现在客户端代码中。就像我们上面的例子,客户端只认识IUser和IDepartment,至于它是Sql Server里的表还是Oracle里的表就不知道了。
缺点:
1、如果你的需求来自增加功能,比如增加Department表,就有点太烦了。首先需要增加IDepartment,SQLServerDepartment,OracleDepartment。然后我们还要去修改工厂类:IFactory,SQLServerFactory,OracleFactory才可以实现,需要修改三个类,实在是有点麻烦。
2、还有就是,客户端程序肯定不止一个,每次都需要声明IFactory factory = new OracleFactory(),如果有100个调用数据库的类,就需要更改100次IFactory factory = new OracleFactory()。
我们将IFactory,SQLServerFactory,OracleFactory三个工厂类都抛弃掉,取而代之的是一个简单工厂类EasyFactory,如下:
public class EasyFactory {
private static String db = "SqlServer";
// private static String db = "Oracle";
public static IUser createUser() {
IUser result = null;
switch (db) {
case "SqlServer":
result = new SqlServerUser();
break;
case "Oracle":
result = new OracleUser();
break;
}
return result;
}
public static IDepartment createDepartment() {
IDepartment result = null;
switch (db) {
case "SqlServer":
result = new SqlServerDepartment();
break;
case "Oracle":
result = new OracleDepartment();
break;
}
return result;
}
}
客户端:
public class EasyClient {
public static void main(String[] args) {
User user = new User();
Department department = new Department();
// 直接得到实际的数据库访问实例,而不存在任何依赖
IUser userOperation = EasyFactory.createUser();
userOperation.getUser(1);
userOperation.insert(user);
// 直接得到实际的数据库访问实例,而不存在任何依赖
IDepartment departmentOperation = EasyFactory.createDepartment();
departmentOperation.insert(department);
departmentOperation.getDepartment(1);
}
}
由于事先在简单工厂类里设置好了db的值,所以简单工厂的方法都不需要由客户端来输入参数,这样在客户端就只需要使用 EasyFactory.createUser(); 和 EasyFactory.createDepartment(); 方法来获得具体的数据库访问类的实例,客户端代码上没有出现任何一个 SqlServer 或 Oracle 的字样,达到了解耦的目的,客户端已经不再受改动数据库访问的影响了。
使用反射的话,我们就可以不需要使用switch,因为使用switch的话,我添加一个Mysql数据库的话,又要switch的话又需要添加case条件。
我们可以根据选择的数据库名称,如“mysql”,利用反射技术自动的获得所需要的实例:
public class EasyFactoryReflect {
private static String packName = "com.zxt.abstractfactory";
private static String sqlName = "Oracle";
public static IUser createUser() throws Exception {
String className = packName + "." + sqlName + "User";
return (IUser) Class.forName(className).newInstance();
}
public static IDepartment createLogin() throws Exception {
String className = packName + "." + sqlName + "Department";
return (IDepartment) Class.forName(className).newInstance();
}
}
以上我们使用简单工厂模式设计的代码中,是用一个字符串类型的db变量来存储数据库名称的,所以变量的值到底是 SqlServer 还是 Oracle,完全可以由事先设置的那个db变量来决定,而我们又可以通过反射来去获取实例,这样就可以去除switch语句了。
在使用反射之后,我们还是需要进EasyFactory中修改数据库类型,还不是完全符合开-闭原则。我们可以通过配置文件来达到目的,每次通过读取配置文件来知道我们应该使用哪种数据库。
如下是一个json类型的配置文件,也可以使用xml类型的配置文件:
{
"packName": " com.zxt.abstractfactory",
"DB": "Oracle"
}
之后就可以通过这个配置文件去找需要加载的类是哪一个。我们通过反射机制+配置文件+简单工厂模式解决了数据库访问时的可维护、可扩展的问题。
Github源代码地址:https://github.com/zengxt/DesignPatterns