工厂方法模式与抽象工厂模式

《大话设计模式》第8、15章读书笔记,介绍工厂方法模式与抽象工厂模式

Posted by GrayWind on November 15, 2018

工厂方法模式

之前讲到过简单工厂模式,它的优点在于工厂类中包含了必要的逻辑判断,根据客户端的选择条件动态实例化相关的类,对于客户端来说,去除了具体产品的依赖。但是针对之前举例的计算程序来说,现在要增加求M的N次方的运算,需要给运算工厂类的方法里加case分支条件,这就违反了开放-封闭原则。所以需要引入工厂方法。

工厂方法模式(Factory Method),定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。

factorymethod

还是针对之前计算器程序的例子,现在将原本的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),提供一个创建一系列相关或依赖对象的接口,而无需指定它们具体稳定类。用来解决涉及到多个产品系列的问题。

abstractfactory

如图所示,有两个或以上的抽象产品,它们各自有不同的实现。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记录
	}
}