大话设计模式笔记(十二)の抽象工厂模式
举个栗子
问题描述
模拟访问数据库“新增用户”和“得到用户”,用户类假设只有id和name两个字段。
简单实现
user
/** * 用户类 * created by callmedevil on 2019/7/28. */ public class user { private int id; private string name; // 省略 get set 方法 }
sqlserveruser
/** * 假设sqlserver 连接,用于操作user表 * created by callmedevil on 2019/7/28. */ public class sqlserveruser { 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; } }
测试
public class test { public static void main(string[] args) { user user = new user(); sqlserveruser su = new sqlserveruser(); su.insert(user); su.getuser(user.getid()); } }
测试结果
在sql server中给user表增加一条记录 在sql server中根据id得到user表一条记录
存在问题
如果需要连接别的数据库,那么这个写法无法扩展,下面使用工厂方法模式实现
工厂方法模式实现
iuser
/** * 用于客户端访问,解除与具体数据库访问的耦合 * created by callmedevil on 2019/7/28. */ public interface iuser { void insert(user user); user getuser(int id); }
sqlserveruser
/** * 用于访问sql server 的user * created by callmedevil on 2019/7/28. */ 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; } }
accessuser
/** * 用于访问access 的user * created by callmedevil on 2019/7/28. */ public class accessuser implements iuser { @override public void insert(user user) { system.out.println("在access 中给user表增加一条记录"); } @override public user getuser(int id) { system.out.println("在在access中根据id得到user表一条记录"); return null; } }
ifactory
/** * 定义一个创建访问user 表对象的抽象工厂接口 * created by callmedevil on 2019/7/28. */ public interface ifactory { iuser createuser(); }
sqlserverfactory
/** * 实现ifactory 接口,实例化sqlserveruser * created by callmedevil on 2019/7/28. */ public class sqlserverfactory implements ifactory { @override public iuser createuser() { return new sqlserveruser(); } }
accessfactory
/** * 实现ifactory 接口,实例化accessuser * created by callmedevil on 2019/7/28. */ public class accessfactory implements ifactory { @override public iuser createuser() { return new accessuser(); } }
测试
public class test { public static void main(string[] args) { user user = new user(); // 若要更改成 access 数据库,只需要将此处改成 // ifactory factory = new accessfactory(); ifactory factory = new sqlserverfactory(); iuser iuser = factory.createuser(); iuser.insert(user); iuser.getuser(1); } }
测试结果同上。
增加需求
如果要增加一个部门表(department),需要怎么改?
修改实现
department
/** * 部门表 * created by callmedevil on 2019/7/28. */ public class department { private int id; private string name; // 省略 get set 方法 }
idepartment
/** * 用于客户端访问,解除与具体数据库访问的耦合 * created by callmedevil on 2019/7/28. */ public interface idepartment { void insert(department department); department getdepartment(int id); }
sqlserverdepartment
/** * 用于访问sqlserver 的department * created by callmedevil on 2019/7/28. */ public class sqlserverdepartment implements idepartment { @override public void insert(department department) { system.out.println("在 sqlserver 中给department 表增加一条记录"); } @override public department getdepartment(int id) { system.out.println("在sql server中根据id得到department表一条记录"); return null; } }
accessdepartment
/** * 用于访问access 的department * created by callmedevil on 2019/7/28. */ public class accessdepartment implements idepartment { @override public void insert(department department) { system.out.println("在access 中给department 表增加一条记录"); } @override public department getdepartment(int id) { system.out.println("在access 中根据id得到department表一条记录"); return null; } }
ifactory
/** * 定义一个创建访问user 表对象的抽象工厂接口 * created by callmedevil on 2019/7/28. */ public interface ifactory { iuser createuser(); idepartment createdepartment(); //增加的接口方法 }
sqlserverfactory
/** * 实现ifactory 接口,实例化sqlserveruser * created by callmedevil on 2019/7/28. */ public class sqlserverfactory implements ifactory { @override public iuser createuser() { return new sqlserveruser(); } @override public idepartment createdepartment() { return new sqlserverdepartment(); //增加了sqlserverdepartment 工厂 } }
accessfactory
/** * 实现ifactory 接口,实例化accessuser * created by callmedevil on 2019/7/28. */ public class accessfactory implements ifactory { @override public iuser createuser() { return new accessuser(); } @override public idepartment createdepartment() { return new accessdepartment(); //增加了accessdepartment 工厂 } }
测试
public class test { public static void main(string[] args) { user user = new user(); department dept = new department(); // 只需确定实例化哪一个数据库访问对象给 factory ifactory factory = new accessfactory(); // 则此时已于具体的数据库访问解除了依赖 iuser iuser = factory.createuser(); iuser.insert(user); iuser.getuser(1); idepartment idept = factory.createdepartment(); idept.insert(dept); idept.getdepartment(1); } }
测试结果
在access 中给user表增加一条记录 在access 中根据id得到user表一条记录 在access 中给department 表增加一条记录 在access 中根据id得到department表一条记录
抽象工厂模式
定义
提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
uml图
代码实现
实际上上面的修改实现已经满足抽象工厂模式的实现方式,此处不再举例。
优缺点
优点
- 最大的好处便是易于交换产品系列,由于不同的具体工厂类,在一个应用中只需要在初始化到时候出现一次,这就使得改变一个应用的具体工厂变得非常容易,它只需要改变具体工厂即可使用不同的产品配置。
- 它让具体的创建实例改成与客户端分离,客户端是通过它们的抽象接口操纵实例,产品的具体类名也被具体工厂的实现分离,不会出现在客户代码中。
缺点
如果还要添加对项目表(project)的访问,那么需要增加三个类,iproject、sqlserverproject、accessproject,还需要更改 ifactory、isqlserverfactory、accessfactory 才可以完全实现,这太糟糕了。编程是门艺术,这样大批量的改动,显然是非常丑陋的做法。
用简单工厂来改进抽象工厂
去除ifactory、sqlserverfactory、accessfactory,改为一个 dataaccess,用一个简单工厂模式来实现。
结构图
代码实现
dataaccess
/** * 统一管理数据库访问 * created by callmedevil on 2019/7/28. */ public class dataaccess { // 数据库名称,可替换成 access private static final string db = "sqlserver"; // private static final string db = "access"; public static iuser createuser() { iuser user = null; switch (db) { case "sqlserver": user = new sqlserveruser(); break; case "access": user = new accessuser(); break; default: break; } return user; } public static idepartment createdepartment() { idepartment department = null; switch (db) { case "sqlserver": department = new sqlserverdepartment(); break; case "access": department = new accessdepartment(); break; default: break; } return department; } }
测试
public class test { public static void main(string[] args) { user user = new user(); department dept = new department(); // 直接得到实际的数据库访问实例,而不存在任何的依赖 iuser iuser = dataaccess.createuser(); iuser.insert(user); iuser.getuser(1); idepartment idept = dataaccess.createdepartment(); idept.insert(dept); idept.getdepartment(1); } }
测试结果
在sql server中给user表增加一条记录 在sql server中根据id得到user表一条记录 在sql server中给department 表增加一条记录 在sql server中根据id得到department表一条记录
存在问题
虽然解决了抽象工厂模式中需要修改太多地方的问题,但又回到了简单工厂模式一开始的问题了,就是如果要连接 oracle 数据库,那么需要修改的地方则是 dataaccess 类中所有方法的 swicth 中加 case 分支了。
用配置文件+反射+抽象工厂实现
配置文件(db.properties)
# 数据库名称,可更改成 access db=sqlserver
dataaccess
/** * 统一管理数据库访问 * created by callmedevil on 2019/7/28. */ public class dataaccess { // 数据库名称,从配置文件中获取 private static string db; public static iuser createuser() throws exception { if (db == null || db.trim() == "") { return null; } // 拼接具体数据库访问类的权限定名 string classname = "com.xxx." + db + "user"; return (iuser) class.forname(classname).newinstance(); } public static idepartment createdeptment() throws exception { if (db == null || db.trim() == "") { return null; } // 拼接具体数据库访问类的权限定名 string classname = "com.xxx." + db + "department"; return (idepartment) class.forname(classname).newinstance(); } public static string getdb() { return db; } public static void setdb(string db) { dataaccess.db = db; } }
测试
public class test { public static void main(string[] args) throws exception { // 加载配置文件 properties properties = new properties(); inputstream is = new fileinputstream(new file("xxx\\db.properties")); // 配置文件所在路径,当前方式采用绝对路径获取 properties.load(is); is.close(); string db = properties.getproperty("db"); // 使用具体的数据库告诉管理类 dataaccess dataaccess = new dataaccess(); dataaccess.setdb(db); user user = new user(); iuser iuser = dataaccess.createuser(); iuser.insert(user); iuser.getuser(1); department dept = new department(); idepartment idept = dataaccess.createdeptment(); idept.insert(dept); idept.getdepartment(1); } }
测试结果
在sql server中给user表增加一条记录 在sql server中根据id得到user表一条记录 在sql server中给department 表增加一条记录 在sql server中根据id得到department表一条记录
现在如果我们增加了 oracle 数据库访问,相关类的增加是不可避免的,这点无论用任何办法都解决不了,不过这叫扩展,开放-封闭原则告诉我们,对于扩展,我们开放,但对于修改,我们应该尽量关闭,就目前实现方式而言,只需要将配置文件中改为 oracle (如果新增的具体访问类名称为 oracleuser 和 oracledepartment 的话)即可达到目的,客户端也不需要任何修改。
反射的好处
所有在用简单工厂的地方,都可以考虑用反射技术来去除 switch 或 if,解除分支判断带来的耦合。
总结
可以发现到目前为止,就“工厂”而言,已经包含了三种设计模式:
- 简单工厂模式
- 工厂方法模式
- 抽象工厂模式
对上述模式的不同点将在后续推出,本文篇幅已经过长,此处先不叙述。