经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » 设计模式 » 查看文章
大话设计模式笔记(十二)の抽象工厂模式
来源:cnblogs  作者:callmeDevil  时间:2019/7/29 9:11:21  对本文有异议

举个栗子

问题描述

模拟访问数据库“新增用户”和“得到用户”,用户类假设只有ID和Name两个字段。

简单实现

User

  1. /**
  2. * 用户类
  3. * Created by callmeDevil on 2019/7/28.
  4. */
  5. public class User {
  6. private int id;
  7. private String name;
  8. // 省略 get set 方法
  9. }

SqlServerUser

  1. /**
  2. * 假设sqlServer 连接,用于操作User表
  3. * Created by callmeDevil on 2019/7/28.
  4. */
  5. public class SqlServerUser {
  6. public void insert(User user){
  7. System.out.println("在SQL Server中给User表增加一条记录");
  8. }
  9. public User getUser(int id){
  10. System.out.println("在SQL Server中根据ID得到User表一条记录");
  11. return null;
  12. }
  13. }

测试

  1. public class Test {
  2. public static void main(String[] args) {
  3. User user = new User();
  4. SqlServerUser su = new SqlServerUser();
  5. su.insert(user);
  6. su.getUser(user.getId());
  7. }
  8. }

测试结果

  1. SQL Server中给User表增加一条记录
  2. SQL Server中根据ID得到User表一条记录

存在问题

如果需要连接别的数据库,那么这个写法无法扩展,下面使用工厂方法模式实现

工厂方法模式实现

IUser

  1. /**
  2. * 用于客户端访问,解除与具体数据库访问的耦合
  3. * Created by callmeDevil on 2019/7/28.
  4. */
  5. public interface IUser {
  6. void insert(User user);
  7. User getUser(int id);
  8. }

SqlServerUser

  1. /**
  2. * 用于访问SQL Server 的User
  3. * Created by callmeDevil on 2019/7/28.
  4. */
  5. public class SqlServerUser implements IUser {
  6. @Override
  7. public void insert(User user) {
  8. System.out.println("在SQL Server中给User表增加一条记录");
  9. }
  10. @Override
  11. public User getUser(int id) {
  12. System.out.println("在SQL Server中根据ID得到User表一条记录");
  13. return null;
  14. }
  15. }

AccessUser

  1. /**
  2. * 用于访问Access 的User
  3. * Created by callmeDevil on 2019/7/28.
  4. */
  5. public class AccessUser implements IUser {
  6. @Override
  7. public void insert(User user) {
  8. System.out.println("在Access 中给User表增加一条记录");
  9. }
  10. @Override
  11. public User getUser(int id) {
  12. System.out.println("在在Access中根据ID得到User表一条记录");
  13. return null;
  14. }
  15. }

IFactory

  1. /**
  2. * 定义一个创建访问User 表对象的抽象工厂接口
  3. * Created by callmeDevil on 2019/7/28.
  4. */
  5. public interface IFactory {
  6. IUser createUser();
  7. }

SqlServerFactory

  1. /**
  2. * 实现IFactory 接口,实例化SQLServerUser
  3. * Created by callmeDevil on 2019/7/28.
  4. */
  5. public class SqlServerFactory implements IFactory {
  6. @Override
  7. public IUser createUser() {
  8. return new SqlServerUser();
  9. }
  10. }

AccessFactory

  1. /**
  2. * 实现IFactory 接口,实例化AccessUser
  3. * Created by callmeDevil on 2019/7/28.
  4. */
  5. public class AccessFactory implements IFactory {
  6. @Override
  7. public IUser createUser() {
  8. return new AccessUser();
  9. }
  10. }

测试

  1. public class Test {
  2. public static void main(String[] args) {
  3. User user = new User();
  4. // 若要更改成 Access 数据库,只需要将此处改成
  5. // IFactory factory = new AccessFactory();
  6. IFactory factory = new SqlServerFactory();
  7. IUser iUser = factory.createUser();
  8. iUser.insert(user);
  9. iUser.getUser(1);
  10. }
  11. }

测试结果同上。

增加需求

如果要增加一个部门表(Department),需要怎么改?

修改实现

Department

  1. /**
  2. * 部门表
  3. * Created by callmeDevil on 2019/7/28.
  4. */
  5. public class Department {
  6. private int id;
  7. private String name;
  8. // 省略 get set 方法
  9. }

IDepartment

  1. /**
  2. * 用于客户端访问,解除与具体数据库访问的耦合
  3. * Created by callmeDevil on 2019/7/28.
  4. */
  5. public interface IDepartment {
  6. void insert(Department department);
  7. Department getDepartment(int id);
  8. }

SqlServerDepartment

  1. /**
  2. * 用于访问SqlServer 的Department
  3. * Created by callmeDevil on 2019/7/28.
  4. */
  5. public class SqlServerDepartment implements IDepartment {
  6. @Override
  7. public void insert(Department department) {
  8. System.out.println("在 SqlServer 中给Department 表增加一条记录");
  9. }
  10. @Override
  11. public Department getDepartment(int id) {
  12. System.out.println("在SQL Server中根据ID得到Department表一条记录");
  13. return null;
  14. }
  15. }

AccessDepartment

  1. /**
  2. * 用于访问Access 的Department
  3. * Created by callmeDevil on 2019/7/28.
  4. */
  5. public class AccessDepartment implements IDepartment {
  6. @Override
  7. public void insert(Department department) {
  8. System.out.println("在Access 中给Department 表增加一条记录");
  9. }
  10. @Override
  11. public Department getDepartment(int id) {
  12. System.out.println("在Access 中根据ID得到Department表一条记录");
  13. return null;
  14. }
  15. }

IFactory

  1. /**
  2. * 定义一个创建访问User 表对象的抽象工厂接口
  3. * Created by callmeDevil on 2019/7/28.
  4. */
  5. public interface IFactory {
  6. IUser createUser();
  7. IDepartment createDepartment(); //增加的接口方法
  8. }

SqlServerFactory

  1. /**
  2. * 实现IFactory 接口,实例化SQLServerUser
  3. * Created by callmeDevil on 2019/7/28.
  4. */
  5. public class SqlServerFactory implements IFactory {
  6. @Override
  7. public IUser createUser() {
  8. return new SqlServerUser();
  9. }
  10. @Override
  11. public IDepartment createDepartment() {
  12. return new SqlServerDepartment(); //增加了SqlServerDepartment 工厂
  13. }
  14. }

AccessFactory

  1. /**
  2. * 实现IFactory 接口,实例化AccessUser
  3. * Created by callmeDevil on 2019/7/28.
  4. */
  5. public class AccessFactory implements IFactory {
  6. @Override
  7. public IUser createUser() {
  8. return new AccessUser();
  9. }
  10. @Override
  11. public IDepartment createDepartment() {
  12. return new AccessDepartment(); //增加了AccessDepartment 工厂
  13. }
  14. }

测试

  1. public class Test {
  2. public static void main(String[] args) {
  3. User user = new User();
  4. Department dept = new Department();
  5. // 只需确定实例化哪一个数据库访问对象给 factory
  6. IFactory factory = new AccessFactory();
  7. // 则此时已于具体的数据库访问解除了依赖
  8. IUser iUser = factory.createUser();
  9. iUser.insert(user);
  10. iUser.getUser(1);
  11. IDepartment iDept = factory.createDepartment();
  12. iDept.insert(dept);
  13. iDept.getDepartment(1);
  14. }
  15. }

测试结果

  1. Access 中给User表增加一条记录
  2. Access 中根据ID得到User表一条记录
  3. Access 中给Department 表增加一条记录
  4. Access 中根据ID得到Department表一条记录

抽象工厂模式

定义

提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。

UML图

代码实现

实际上上面的修改实现已经满足抽象工厂模式的实现方式,此处不再举例。

优缺点

优点

  • 最大的好处便是易于交换产品系列,由于不同的具体工厂类,在一个应用中只需要在初始化到时候出现一次,这就使得改变一个应用的具体工厂变得非常容易,它只需要改变具体工厂即可使用不同的产品配置
  • 让具体的创建实例改成与客户端分离,客户端是通过它们的抽象接口操纵实例,产品的具体类名也被具体工厂的实现分离,不会出现在客户代码中

缺点

如果还要添加对项目表(Project)的访问,那么需要增加三个类,IProject、SQLServerProject、AccessProject,还需要更改 IFactory、ISQLServerFactory、AccessFactory 才可以完全实现,这太糟糕了。编程是门艺术,这样大批量的改动,显然是非常丑陋的做法。

用简单工厂来改进抽象工厂

去除IFactory、SQLServerFactory、AccessFactory,改为一个 DataAccess,用一个简单工厂模式来实现。

结构图

代码实现

DataAccess

  1. /**
  2. * 统一管理数据库访问
  3. * Created by callmeDevil on 2019/7/28.
  4. */
  5. public class DataAccess {
  6. // 数据库名称,可替换成 Access
  7. private static final String DB = "SqlServer";
  8. // private static final String DB = "Access";
  9. public static IUser createUser() {
  10. IUser user = null;
  11. switch (DB) {
  12. case "SqlServer":
  13. user = new SqlServerUser();
  14. break;
  15. case "Access":
  16. user = new AccessUser();
  17. break;
  18. default:
  19. break;
  20. }
  21. return user;
  22. }
  23. public static IDepartment createDepartment() {
  24. IDepartment department = null;
  25. switch (DB) {
  26. case "SqlServer":
  27. department = new SqlServerDepartment();
  28. break;
  29. case "Access":
  30. department = new AccessDepartment();
  31. break;
  32. default:
  33. break;
  34. }
  35. return department;
  36. }
  37. }

测试

  1. public class Test {
  2. public static void main(String[] args) {
  3. User user = new User();
  4. Department dept = new Department();
  5. // 直接得到实际的数据库访问实例,而不存在任何的依赖
  6. IUser iUser = DataAccess.createUser();
  7. iUser.insert(user);
  8. iUser.getUser(1);
  9. IDepartment iDept = DataAccess.createDepartment();
  10. iDept.insert(dept);
  11. iDept.getDepartment(1);
  12. }
  13. }

测试结果

  1. SQL Server中给User表增加一条记录
  2. SQL Server中根据ID得到User表一条记录
  3. SQL Server中给Department 表增加一条记录
  4. SQL Server中根据ID得到Department表一条记录

存在问题

虽然解决了抽象工厂模式中需要修改太多地方的问题,但又回到了简单工厂模式一开始的问题了,就是如果要连接 Oracle 数据库,那么需要修改的地方则是 DataAccess 类中所有方法的 swicth 中加 case 分支了。

用配置文件+反射+抽象工厂实现

配置文件(db.properties)

  1. # 数据库名称,可更改成 Access
  2. db=SqlServer

DataAccess

  1. /**
  2. * 统一管理数据库访问
  3. * Created by callmeDevil on 2019/7/28.
  4. */
  5. public class DataAccess {
  6. // 数据库名称,从配置文件中获取
  7. private static String DB;
  8. public static IUser createUser() throws Exception {
  9. if (DB == null || DB.trim() == "") {
  10. return null;
  11. }
  12. // 拼接具体数据库访问类的权限定名
  13. String className = "com.xxx." + DB + "User";
  14. return (IUser) Class.forName(className).newInstance();
  15. }
  16. public static IDepartment createDeptment() throws Exception {
  17. if (DB == null || DB.trim() == "") {
  18. return null;
  19. }
  20. // 拼接具体数据库访问类的权限定名
  21. String className = "com.xxx." + DB + "Department";
  22. return (IDepartment) Class.forName(className).newInstance();
  23. }
  24. public static String getDB() {
  25. return DB;
  26. }
  27. public static void setDB(String DB) {
  28. DataAccess.DB = DB;
  29. }
  30. }

测试

  1. public class Test {
  2. public static void main(String[] args) throws Exception {
  3. // 加载配置文件
  4. Properties properties = new Properties();
  5. InputStream is = new FileInputStream(new File("xxx\\db.properties")); // 配置文件所在路径,当前方式采用绝对路径获取
  6. properties.load(is);
  7. is.close();
  8. String db = properties.getProperty("db");
  9. // 使用具体的数据库告诉管理类
  10. DataAccess dataAccess = new DataAccess();
  11. dataAccess.setDB(db);
  12. User user = new User();
  13. IUser iUser = dataAccess.createUser();
  14. iUser.insert(user);
  15. iUser.getUser(1);
  16. Department dept = new Department();
  17. IDepartment iDept = dataAccess.createDeptment();
  18. iDept.insert(dept);
  19. iDept.getDepartment(1);
  20. }
  21. }

测试结果

  1. SQL Server中给User表增加一条记录
  2. SQL Server中根据ID得到User表一条记录
  3. SQL Server中给Department 表增加一条记录
  4. SQL Server中根据ID得到Department表一条记录

现在如果我们增加了 Oracle 数据库访问,相关类的增加是不可避免的,这点无论用任何办法都解决不了,不过这叫扩展,开放-封闭原则告诉我们,对于扩展,我们开放,但对于修改,我们应该尽量关闭,就目前实现方式而言,只需要将配置文件中改为 Oracle (如果新增的具体访问类名称为 OracleUserOracleDepartment 的话)即可达到目的,客户端也不需要任何修改。

反射的好处

所有在用简单工厂的地方,都可以考虑用反射技术来去除 switch 或 if,解除分支判断带来的耦合。

总结

可以发现到目前为止,就“工厂”而言,已经包含了三种设计模式:

  • 简单工厂模式
  • 工厂方法模式
  • 抽象工厂模式

对上述模式的不同点将在后续推出,本文篇幅已经过长,此处先不叙述。

原文链接:http://www.cnblogs.com/call-me-devil/p/11259982.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号