工厂方法模式
之前讲到过简单工厂模式,它的优点在于工厂类中包含了必要的逻辑判断,根据客户端的选择条件动态实例化相关的类,对于客户端来说,去除了具体产品的依赖。但是针对之前举例的计算程序来说,现在要增加求M的N次方的运算,需要给运算工厂类的方法里加case分支条件,这就违反了开放-封闭原则。所以需要引入工厂方法。
工厂方法模式(Factory Method),定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。
还是针对之前计算器程序的例子,现在将原本的OperationFactory抽象为一个工厂接口
public interface IFactory {
public Operation createOperation();
}
然后对于加减乘除四个操作分别增加对应的具体工厂实现,分别返回对应的Operation
public class AddFactory implements IFactory {
@Override
public Operation createOperation() {
return new OperationAdd();
}
}
public class SubFactory implements IFactory {
@Override
public Operation createOperation() {
return new OperationSub();
}
}
public class MulFactory implements IFactory {
@Override
public Operation createOperation() {
return new OperationMul();
}
}
public class DivFactory implements IFactory {
@Override
public Operation createOperation() {
return new OperationDiv();
}
}
客户端使用时,根据需要构造具体的工厂来获取Operation接口。
public class Test {
public static void main(String[] args) throws Exception {
IFactory factory = new AddFactory();
Operation op = factory.createOperation();
double result = op.getResult(10, 5);
System.out.println(result);
}
}
需要扩展时,只需要增加IFactory的实现类即可。工厂方式模式实现时,客户端需要决定实例化哪一个工厂来实现运算类,选择判断的问题还是存在的,也就是说,工厂方法把简单工厂的内部逻辑判断移到了客户端代码来进行。想增加功能时,本来是改工厂类,现在是修改客户端。
抽象工厂模式
抽象工厂模式(Abstract Factory),提供一个创建一系列相关或依赖对象的接口,而无需指定它们具体稳定类。用来解决涉及到多个产品系列的问题。
如图所示,有两个或以上的抽象产品,它们各自有不同的实现。IFactory是一个抽象工厂接口,它里面应该包含所有的产品创建的抽象方法。而ConcreteFactory1和ConcreteFactory2是具体的工厂,它们负责创建不同的具体产品。通常是在运行时刻再创建一个ConcreteFactory类的实例,这个具体的工厂再创建具有特定实现的产品对象,也就是说,为了创建不同的产品对象,客户端应使用不同的具体工厂。
具体例子来说,现在有一个业务代码包含两张数据库表User和Department,它们相当于抽象产品。需要通过数据库客户端来访问,它作为抽象工厂。现在存在Oracle和MySQL两种数据库的客户端,它们是具体工厂,负责产生OracleUser、OracleDepartment和MySQLUser、MySQLDepartment。具体产品包含了查找、插入、修改、删除等具体方法。
User和Department是具体的数据对象,简化一下他们都只有id和name两个属性
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;
}
}
//Department相同省略
IUser和IDepartment是对数据库操作的抽象接口,它们都有插入和查找两个方法
public interface IUser {
public void insert(User user);
public User getUser(int id);
}
public interface IDepartment {
public void insert(Department department);
public Department getDepartment(int id);
}
根据数据库,具体实现类如下
public class MysqlUser implements IUser {
@Override
public void insert(User user) {
System.out.println("在MySQL中插入一条User记录");
}
@Override
public User getUser(int id) {
System.out.println("在MySQL中获取一条User记录");
return null;
}
}
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中获取一条User记录");
return null;
}
}
//Department类似
IFactory是抽象工厂接口,需要定义Department和User各自的建造方法
public interface IFactory {
public IUser createUser();
public IDepartment createDepartment();
}
具体工厂类如下,分别构造不同的产品实现类
public class OracleFactory implements IFactory {
@Override
public IUser createUser() {
return new OracleUser();
}
@Override
public IDepartment createDepartment() {
return new OracleDepartment();
}
}
public class MysqlFactory implements IFactory {
@Override
public IUser createUser() {
return new MysqlUser();
}
@Override
public IDepartment createDepartment() {
return new MysqlDepartment();
}
}
客户端代码如下
public class AbstractFactoryTest {
public static void main(String[] args) {
User user = new User();
IFactory factory = new OracleFactory();
IUser iu = factory.createUser();
iu.insert(user);//在Oracle中插入一条User记录
iu.getUser(1);//在Oracle中获取一条User记录
Department department = new Department();
factory = new MysqlFactory();
IDepartment id = factory.createDepartment();
id.insert(department);//在MySQL中插入一条Department记录
id.getDepartment(1);//在MySQL中获取一条Department记录
}
}
抽象工厂的好处是易于交换产品系列,由于具体工厂类在一个应用中只需要在初始化的时候出现一次,这就使得改变一个应用的具体工厂变得非常容易,它只需要改变具体工厂即可使用不同的产品配置。其次,它让具体的创建实例与客户端分离,客户端是通过他们的接口操纵实例,产品的具体类名也被具体工厂的实现分离,不会出现在客户端代码中。但是,当我们需要增加项目表Project时,需要增加IProject、OracleProject、MySQLProject,同时需要修改抽象工厂类和具体工厂类增加对应的创建方法。并且,如果现在有100个客户端需要从Oracle转向MySQL,它们都需要把声明的IFactroy factroy = new OracleUser()改为new MysqlUser(),这显然是不合理的。
可以通过简单工厂增加DataAccess,由switch来根据数据库类型创建不同的对象,但是当增加新的数据库如SQL Sever的时候,就需要去修改简单工厂类。
public class DataAccess {
public static IUser createUser(String db) {
IUser result = null;
switch (db) {
case "Oracle":
result = new OracleUser();
break;
case "MySQL":
result = new MysqlUser();
break;
default:
}
return result;
}
public static IDepartment createDepartment(String db) {
IDepartment result = null;
switch (db) {
case "Oracle":
result = new OracleDepartment();
break;
case "MySQL":
result = new MysqlDepartment();
break;
default:
}
return result;
}
public static void main(String[] args) {
User user = new User();
Department department = new Department();
IUser iu = DataAccess.createUser("Oracle");
iu.insert(user);//在Oracle中插入一条User记录
iu.getUser(1);//在Oracle中获取一条User记录
IDepartment id = DataAccess.createDepartment("MySQL");
id.insert(department);//在MySQL中插入一条Department记录
id.getDepartment(1);//在MySQL中获取一条Department记录
}
}
借助反射的方法,可以实现依赖注入(DI)来解决这个问题。Java中使用反射需要给出类的完整包路径,在spring IOC功能中,通常可以通过xml配置文件来给出类的完整路径。
public class Reflection {
public static IUser createUser(String path) {
IUser result = null;
try {
@SuppressWarnings("unchecked")
Class<IUser> claz = (Class<IUser>) Class.forName(path);
result = claz.newInstance();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return result;
}
public static IDepartment createDepartment(String path) {
IDepartment result = null;
try {
@SuppressWarnings("unchecked")
Class<IDepartment> claz = (Class<IDepartment>) Class.forName(path);
result = claz.newInstance();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return result;
}
public static void main(String[] args) {
User user = new User();
Department department = new Department();
IUser iu = Reflection.createUser("graywind.design.abstractfactory.MysqlUser");
iu.insert(user);//在MySQL中插入一条User记录
iu.getUser(1);//在MySQL中获取一条User记录
IDepartment id = Reflection.createDepartment("graywind.design.abstractfactory.OracleDepartment");
id.insert(department);//在Oracle中插入一条Department记录
id.getDepartment(1);//在Oracle中获取一条Department记录
}
}