大话设计模式笔记(十二)の抽象工厂模式

举个栗子

问题描述

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

简单实现

User

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

SqlServerUser

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

测试

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

测试结果

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

存在问题

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

工厂方法模式实现

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.  }
9.  

SqlServerUser

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

AccessUser

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

IFactory

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

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.  }
11.  

测试

 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.  }
12.  

测试结果同上。

增加需求

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

修改实现

Department

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

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.  }
9.  

SqlServerDepartment

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

AccessDepartment

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

IFactory

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

SqlServerFactory

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

AccessFactory

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

测试

 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.  
12.          IDepartment iDept = factory.createDepartment();
13.          iDept.insert(dept);
14.          iDept.getDepartment(1);
15.      }
16.  }
17.  

测试结果

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

抽象工厂模式

定义

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

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.  
 7.      // 数据库名称,可替换成 Access
 8.      private static final String DB = "SqlServer";
 9.  //    private static final String DB = "Access";
10.  
11.      public static IUser createUser() {
12.          IUser user = null;
13.          switch (DB) {
14.              case "SqlServer":
15.                  user = new SqlServerUser();
16.                  break;
17.              case "Access":
18.                  user = new AccessUser();
19.                  break;
20.              default:
21.                  break;
22.          }
23.          return user;
24.      }
25.  
26.      public static IDepartment createDepartment() {
27.          IDepartment department = null;
28.          switch (DB) {
29.              case "SqlServer":
30.                  department = new SqlServerDepartment();
31.                  break;
32.              case "Access":
33.                  department = new AccessDepartment();
34.                  break;
35.              default:
36.                  break;
37.          }
38.          return department;
39.      }
40.  
41.  }
42.  

测试

 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.  
10.          IDepartment iDept = DataAccess.createDepartment();
11.          iDept.insert(dept);
12.          iDept.getDepartment(1);
13.      }
14.  }
15.  

测试结果

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

存在问题

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

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

配置文件(db.properties)

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

DataAccess

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

测试

 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.  
13.          User user = new User();
14.          IUser iUser = dataAccess.createUser();
15.          iUser.insert(user);
16.          iUser.getUser(1);
17.  
18.          Department dept = new Department();
19.          IDepartment iDept = dataAccess.createDeptment();
20.          iDept.insert(dept);
21.          iDept.getDepartment(1);
22.      }
23.  }
24.  

测试结果

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

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

反射的好处

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

总结

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

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

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

posted @   callmeDevil  阅读(520)  评论(0编辑  收藏  举报
编辑推荐:
· 智能桌面机器人:用.NET IoT库控制舵机并多方法播放表情
· Linux glibc自带哈希表的用例及性能测试
· 深入理解 Mybatis 分库分表执行原理
· 如何打造一个高并发系统?
· .NET Core GC压缩(compact_phase)底层原理浅谈
阅读排行:
· 手把手教你在本地部署DeepSeek R1,搭建web-ui ,建议收藏!
· 新年开篇:在本地部署DeepSeek大模型实现联网增强的AI应用
· Janus Pro:DeepSeek 开源革新,多模态 AI 的未来
· 互联网不景气了那就玩玩嵌入式吧,用纯.NET开发并制作一个智能桌面机器人(三):用.NET IoT库
· 【非技术】说说2024年我都干了些啥
点击右上角即可分享
微信分享提示