大话设计模式笔记(十二)の抽象工厂模式
举个栗子
问题描述
模拟访问数据库“新增用户”和“得到用户”,用户类假设只有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 (如果新增的具体访问类名称为 OracleUser 和 OracleDepartment 的话)即可达到目的,客户端也不需要任何修改。
反射的好处
所有在用简单工厂的地方,都可以考虑用反射技术来去除 switch 或 if,解除分支判断带来的耦合。
总结
可以发现到目前为止,就“工厂”而言,已经包含了三种设计模式:
- 简单工厂模式
- 工厂方法模式
- 抽象工厂模式
对上述模式的不同点将在后续推出,本文篇幅已经过长,此处先不叙述。
Pass:以上纯属个人理解~~如果发现有错或是心存建议意见等,欢迎大家评论或联系~(# ゚Д゚)~祝大家身体健康学习进步工作顺利生活愉快!
版权归 callmeDevil 所有,如需转载请标注转载来源
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 智能桌面机器人:用.NET IoT库控制舵机并多方法播放表情
· Linux glibc自带哈希表的用例及性能测试
· 深入理解 Mybatis 分库分表执行原理
· 如何打造一个高并发系统?
· .NET Core GC压缩(compact_phase)底层原理浅谈
· 手把手教你在本地部署DeepSeek R1,搭建web-ui ,建议收藏!
· 新年开篇:在本地部署DeepSeek大模型实现联网增强的AI应用
· Janus Pro:DeepSeek 开源革新,多模态 AI 的未来
· 互联网不景气了那就玩玩嵌入式吧,用纯.NET开发并制作一个智能桌面机器人(三):用.NET IoT库
· 【非技术】说说2024年我都干了些啥