抽象工厂模式
抽象工厂模式(Abstract Factory),提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
客户端通过不同的工厂来创建不同的产品。抽象工厂接口包含了所有产品创建的抽象方法。在创建具体产品的时候,使用具体的工厂。
AbstractFactory:抽象工厂,包含所有产品创建的抽象方法。
ConcreteFactory1和ConcreteFactory2:具体的工厂。
AbstractProductA和AbstractProductB:两个抽象产品。
ProductA1,ProductA2 和 ProductB1,ProductB2 就是对两个抽象产品的具体分类的实现。
示例:两套不同的数据库
在项目中使用两套不同的数据库,在客户端中使用抽象工厂模式,屏蔽数据库的差异。
使用抽象工厂模式,在抽象工厂接口中定义创建所有产品的抽象方法接口。在每个具体的工厂中实现,创建不同的产品。在客户端中使用抽象工厂接口和抽象产品,屏蔽具体的细节,面向接口,而不是面向实现。
工厂接口
public interface IFactory {
public IUser createUser();
public IDepartment createDepartment();
}
// Sqlserver 工厂
public class SqlserverFactory implements IFactory {
public IUser createUser(){
return new SqlserverUser();
}
public IDepartment createDepartment(){
return new SqlserverDepartment();
}
}
// Access工厂
public class AccessFactory implements IFactory {
public IUser createUser(){
return new AccessUser();
}
public IDepartment createDepartment(){
return new AccessDepartment();
}
}
用户类接口,定义了所有的抽象方法
public interface IUser {
public void insert(User user);
public User getUser(int id);
}
Sqlserver 数据库实现
public class SqlserverUser implements IUser {
//新增一个用户
public void insert(User user){
System.out.println("在SQL Server中给User表增加一条记录");
}
//获取一个用户信息
public User getUser(int id){
System.out.println("在SQL Server中根据用户ID得到User表一条记录");
return null;
}
}
Access 数据库实现
public class AccessUser implements IUser {
//新增一个用户
public void insert(User user){
System.out.println("在Access中给User表增加一条记录");
}
//获取一个用户信息
public User getUser(int id){
System.out.println("在Access中根据用户ID得到User表一条记录");
return null;
}
}
部门类接口
public interface IDepartment {
public void insert(Department department);
public Department getDepartment(int id);
}
Sqlserver 数据库实现
public class SqlserverDepartment implements IDepartment {
//新增一个部门
public void insert(Department department){
System.out.println("在SQL Server中给Department表增加一条记录");
}
//获取一个部门信息
public Department getDepartment(int id){
System.out.println("在SQL Server中根据部门ID得到Department表一条记录");
return null;
}
}
Access 数据库实现
public class AccessDepartment implements IDepartment {
//新增一个部门
public void insert(Department department){
System.out.println("在Access中给Department表增加一条记录");
}
//获取一个部门信息
public Department getDepartment(int id){
System.out.println("在Access中根据部门ID得到Department表一条记录");
return null;
}
}
客户端
public class Test {
public static void main(String[] args){
System.out.println("**********************************************");
System.out.println("《大话设计模式》代码样例");
System.out.println();
// 实体类user,实体类department
User user = new User();
Department department = new Department();
// 如果使用的是 sqlserver数据库,就用对应的工厂,创建出操作user和department的对象
IFactory factory = new SqlserverFactory();
//IFactory factory = new AccessFactory(); 使用是Access数据库
// 使用 IUser接口,屏蔽了具体的对象
IUser iu = factory.createUser();
iu.insert(user); //新增一个用户
iu.getUser(1); //得到用户ID为1的用户信息
IDepartment idept = factory.createDepartment();
idept.insert(department); //新增一个部门
idept.getDepartment(2); //得到部门ID为2的用户信息
System.out.println();
System.out.println("**********************************************");
}
}
在上面代码中,有User类和操作User类的IUser接口,有Department类和操作Department类的IDepartment接口,每个接口下面又有两个大的分类,分别是 SqlServer数据库操作类 和 Access数据库的操作类。
解决这种多个产品系列的问题,就需要使用抽象工厂模式。
抽象工厂模式的优缺点
从上面的代码中可以看出,最大的好处是易于交换产品系列,如果使用的是 SqlServer 数据库,则只需要 IFactory factory = new SqlserverFactory(),如果使用的是 Access数据库,则只需要更改为对应的具体工厂,创建出对不同产品的操作对象。、
第二个好处,让具体的创建实例过程和客户端分离,客户端通过抽象接口来操纵实例,不会出现产品的具体类名。如上面对 User 类对象实现操纵的 IUser接口,客户端不必知道具体的操纵类,客户端只需认识 IUser和IDepartment。
简单工厂来改进抽象工厂
在上面代码中,如果要增加一个新的产品 Project ,就需要增加三个类,IProJect,SqlserverProject,AccessProject,还需要在IFactory,SqlserverFactory,AccessFactory中增加创建这个产品的方法。
这种改动是非常大的。
编程是门艺术,这样大批量的改动,是非常丑陋的做法。
增加 DataAccess类,来代替 IFactory,SqlserverFactory,AccessFactory三个工厂类。
DataAccess类是一个简单工厂,里面有创建所有产品的方法,在创建产品的时候根据不同的数据库创建出相应的产品。
DataAccess 类
public class DataAccess {
private static String db = "Sqlserver";//数据库名称,可替换成Access
//private static String db ="Access";
//创建用户对象工厂
public static IUser createUser(){
IUser result = null;
switch(db){
case "Sqlserver":
result = new SqlserverUser();
break;
case "Access":
result = new AccessUser();
break;
}
return result;
}
//创建部门对象工厂
public static IDepartment createDepartment(){
IDepartment result = null;
switch(db){
case "Sqlserver":
result = new SqlserverDepartment();
break;
case "Access":
result = new AccessDepartment();
break;
}
return result;
}
}
客户端
public class Test {
public static void main(String[] args){
System.out.println("**********************************************");
System.out.println("《大话设计模式》代码样例");
System.out.println();
User user = new User();
Department department = new Department();
//直接得到实际的数据库访问实例,而不存在任何依赖
IUser iu = DataAccess.createUser();
iu.insert(user); //新增一个用户
iu.getUser(1); //得到用户ID为1的用户信息
//直接得到实际的数据库访问实例,而不存在任何依赖
IDepartment idept = DataAccess.createDepartment();
idept.insert(department); //新增一个部门
idept.getDepartment(2); //得到部门ID为2的用户信息
System.out.println();
System.out.println("**********************************************");
}
}
这时,如果想要增加相应的产品,只需要在 DataAccess 类中增加创建产品的方法。
反射实例化对象
反射获取实例化对象的方式,格式如下
Object result = Class.forName("包名.类名").getDeclaredConstructor().newInstance();
可以将包名类名作为变量,来在程序的运行时,创建需要的对象实例。
使用反射方式,重写 DataAccess 类。
public class DataAccess {
// 包名
private static String assemblyName = "code.chapter15.abstractfactory5.";
// 类名
private static String db = "Sqlserver";//数据库名称,可替换成Access
//创建用户对象工厂
public static IUser createUser() {
return (IUser)getInstance(assemblyName + db + "User");
}
//创建部门对象工厂
public static IDepartment createDepartment(){
return (IDepartment)getInstance(assemblyName + db + "Department");
}
private static Object getInstance(String className){
Object result = null;
try{
result = Class.forName(className).getDeclaredConstructor().newInstance();
}
catch (InvocationTargetException e) {
e.printStackTrace();
}
catch (NoSuchMethodException e) {
e.printStackTrace();
}
catch (InstantiationException e) {
e.printStackTrace();
}
catch (IllegalAccessException e) {
e.printStackTrace();
}
catch (ClassNotFoundException e) {
e.printStackTrace();
}
return result;
}
}
通过反射的方式创建对象,避免了在程序使用大量的 switch 语句。
在上面代码中,如果想要改变数据库,需要改写这个变量,重写编译程序。这样是不太好的,违法了开放封闭原则。可以使用配置文件来指明数据库,这样如果想要改变数据库,只需要改写配置文件即可,不用重写编译代码。
private static String db = "Sqlserver";//数据库名称,可替换成Access
添加一个dp.properties文件,指明要使用的数据库。
db=Sqlserver
接下来更改 DataAccess类。
public class DataAccess {
private static String assemblyName = "code.chapter15.abstractfactory6.";
// 读取配置文件的方法。
public static String getDb() {
String result="";
try{
Properties properties = new Properties();
//编译后,请将db.properties文件复制到要编译的class目录中,并确保下面path路径与
//实际db.properties文件路径一致。否则会报No such file or directory错误
String path=System.getProperty("user.dir")+"/code/chapter15/abstractfactory6/db.properties";
System.out.println("path:"+path);
BufferedReader bufferedReader = new BufferedReader(new FileReader(path));
properties.load(bufferedReader);
result = properties.getProperty("db");
}
catch(IOException e){
e.printStackTrace();
}
return result;
}
//创建用户对象工厂
public static IUser createUser() {
String db=getDb();
return (IUser)getInstance(assemblyName + db + "User");
}
//创建部门对象工厂
public static IDepartment createDepartment(){
String db=getDb();
return (IDepartment)getInstance(assemblyName + db + "Department");
}
private static Object getInstance(String className){
Object result = null;
try{
result = Class.forName(className).getDeclaredConstructor().newInstance();
}
catch (InvocationTargetException e) {
e.printStackTrace();
}
catch (NoSuchMethodException e) {
e.printStackTrace();
}
catch (InstantiationException e) {
e.printStackTrace();
}
catch (IllegalAccessException e) {
e.printStackTrace();
}
catch (ClassNotFoundException e) {
e.printStackTrace();
}
return result;
}
}
经过以上的修改,要想更换数据库,只要修改配置文件即可。
在所有的用到简单工厂的地方,都可以用反射来去除switch语句,解除分支判断带来的耦合。
使用反射修改商场收银程序
在商场收银代码中使用了简单工厂来创建对象,简单工厂中使用了switch语句,可以使用反射消除switch语句。
增加配置文件,定义策略类
strategy1=CashRebateReturnFactory,1d,0d,0d
strategy2=CashRebateReturnFactory,0.8d,0d,0d
strategy3=CashRebateReturnFactory,0.7d,0d,0d
strategy4=CashRebateReturnFactory,1d,300d,100d
strategy5=CashRebateReturnFactory,0.8d,300d,100d
strategy6=CashReturnRebateFactory,0.7d,200d,50d
使用了反射的 CashContext类
public class CashContext {
private static String assemblyName = "code.chapter15.abstractfactory7.";
private ISale cs; //声明一个ISale接口对象
//通过构造方法,传入具体的收费策略
public CashContext(int cashType){
// 读取配置信息
String[] config = getConfig(cashType).split(",");
// 反射创建工厂实例
IFactory fs = getInstance(config[0],
Double.parseDouble(config[1]),
Double.parseDouble(config[2]),
Double.parseDouble(config[3]));
// 工厂实例创建策略类
this.cs = fs.createSalesModel();
}
//通过文件得到销售策略的配置文件
private String getConfig(int number) {
String result="";
try{
Properties properties = new Properties();
String path=System.getProperty("user.dir")+"/code/chapter15/abstractfactory7/data.properties";
System.out.println("path:"+path);
BufferedReader bufferedReader = new BufferedReader(new FileReader(path));
properties.load(bufferedReader);
result = properties.getProperty("strategy"+number);
}
catch(IOException e){
e.printStackTrace();
}
return result;
}
//根据配置文件获得相关的对象实例
private IFactory getInstance(String className,double a,double b,double c){
IFactory result = null;
try{
result = (IFactory)Class.forName(assemblyName+className)
.getDeclaredConstructor(new Class[]{double.class,double.class,double.class})
.newInstance(new Object[]{a,b,c});
}
catch (InvocationTargetException e) {
e.printStackTrace();
}
catch (NoSuchMethodException e) {
e.printStackTrace();
}
catch (InstantiationException e) {
e.printStackTrace();
}
catch (IllegalAccessException e) {
e.printStackTrace();
}
catch (ClassNotFoundException e) {
e.printStackTrace();
}
return result;
}
public double getResult(double price,int num){
//根据收费策略的不同,获得计算结果
return this.cs.acceptCash(price,num);
}
}
这样更改销售策略,只需要修改配置文件即可,不需要修改代码。做到了开闭原则。
一个程序员如果从来没有熬夜写程序的经历,不能算作一个好程序员,因为他没有痴迷过,所以不会有大成就。