抽象工厂模式(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,实体类departmentUser 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);}
}
这样更改销售策略,只需要修改配置文件即可,不需要修改代码。做到了开闭原则。
一个程序员如果从来没有熬夜写程序的经历,不能算作一个好程序员,因为他没有痴迷过,所以不会有大成就。
上一篇:【专项训练】贪心算法
下一篇:Anaconda 常见命令汇总