当前位置: 首页 > news >正文

【设计模式】抽象工厂模式

系列文章目录


文章目录

  • 系列文章目录
    • 最基本的数据访问程序
    • 工厂方法模式的数据访问程序
    • 抽象工厂模式的数据访问程序
    • 抽象工厂模式
    • 抽象工厂模式的优点与缺点
    • 用简单工厂来改进抽象工厂
    • 用反射 + 抽象工厂的数据访问程序
    • 用反射 + 配置文件实现数据访问程序
  • 总结

在这里插入图片描述

我们知道数据库之间调用代码相差很大,如果我们同一个项目只需要换数据库,在不同数据库的语法不同会冲突,包括一些关键字也是不同的,我们如何用抽象工厂模式来解决呢?

最基本的数据访问程序

我们写一段原来数据访问的做法,例如新增用户和得到用户。

用户类,假设只有ID和Name两个字段,其余省略

  //用户类public class User{//用户IDprivate int _id;public int getId(){return this._id;}public void setId(int value){this._id = value;}//用户姓名private String _name;public String getName(){return this._name;}public void setName(String value){this._name = value;}}

SqlServerUser类 ---- 用于操作User表,假设只有新增用户和得到用户方法,其余方法以及具体SQL语句省略。

  //SqlServerUser类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 static void main(String[] args) {User user = new User();SqlServerUser su = new SqlServerUser();su.insert(user);su.getUser(1);}

这里之所以不能换数据库,原因就在于SqlServerUser su = new SqlServerUser()使得su这个对象被框死在SQL Server上了,他本质上也是在使用SQL Server的SQL语句代码,这里毫无疑问存在耦合。如果这里是灵活的,是多态的,那么在执行‘su.insert(user)’和’su.getUser(1)'时就不用考虑是在用SQL Server还是在用Access.

所以,我们可以用工厂方法模式来封装new SqlServerUser()所造成的变化。**工厂方法模式是定义一个用于创建对象的接口,让子类决定实例化哪一个类。 **

工厂方法模式的数据访问程序

IUser接口:用于客户端访问,解除与具体数据库访问的耦合

  //用户类接口public interface IUser{public void insert(User user);public User getUser(int id);}

SqlserverUser类:用于访问SQL server的User

  public class SqlserverUser implements IUser{//新增一个用户@Overridepublic void insert(User user) {System.out.println("在SQL Server中给User变增加一条记录");}//获取一个用户信息@Overridepublic User getUser(int id) {System.out.println("在SQL Server中根据用户ID得到User表一条记录");return null;}}

AccessUser类:用于访问Access的User。

  public class AccessUser implements IUser{//新增一个用户@Overridepublic void insert(User user) {System.out.println("在Access中给User表增加一些记录");}//获取一个用户信息@Overridepublic User getUser(int id) {System.out.println("在Access中根据用户ID得到User表一条记录");return null;}}

IFactory接口:定义一个创建访问User表对象的抽象工厂接口

   public interface IFactory{public IUser createUser();}

SqlServerFactory类:实现IFactory接口,实例化SqlserverUser。

   public class SqlserverFactory implements IFactory{@Overridepublic IUser createUser() {return new SqlserverUser();}}

AccessFactory类:实现IFactory接口,实例化AccessUser。

  public class AccessFactory implements IFactory{@Overridepublic IUser createUser() {return new AccessUser();}}

客户端代码:

  public static void main(String[] args) {User user = new User();IFactory factory = new SqlserverFactory();IUser iu = factory.createUser();iu.insert(user);iu.getUser(1);}

现在如果非要换数据库,只需要把new SqlserverFactory()改成new AccessFactory(),此时由于多态的关系,使得声明IUser接口的对象iu事先根本不知道是在访问那个数据库,却可以在运行时很好的完成工作,这就是所谓的业务数据与数据访问的解耦

但是这样在代码里还是有指明‘new SqlserverFactory()’,所以我们在更换数据库时,更改的还是很多。另外数据库不可能只有一个user表,如果有多个表应该怎么办的,我们接下来一一为大家解释。

若有多个表:

   public class Department{//部门idprivate int _id;//部门名称private String _name;public int get_id() {return _id;}public void set_id(int _id) {this._id = _id;}public String get_name() {return _name;}public void set_name(String _name) {this._name = _name;}}

此时我们需要增加好多类,但是还是有一定灵活性

抽象工厂模式的数据访问程序

增加了关于部门表的处理

IDepartment接口:用于客户端访问,解除与具体数据库访问的耦合

  public interface IDepartment{public void insert(Department department);public Department getDepartment(int id);}

SqlserverDepartment类:用于访问SQL Server的Department。

  public class SqlserverDepartment implements IDepartment{//新增一个部门@Overridepublic void insert(Department department) {System.out.println("在SQL Server中给Department表添加一条数据");}//获取一个部门信息@Overridepublic Department getDepartment(int id) {System.out.println("在Access中根据部门id得到Department表一条记录");return null;}}

AccessDepartment类:用于访问Access的Department。

  public class AccessDepartment implements  IDepartment{@Overridepublic void insert(Department department) {System.out.println("在Access表中添加一条记录");}@Overridepublic Department getDepartment(int id) {System.out.println("在Access中根据部门ID得到Department表一条记录");return null;}}

IFactory接口:定义一个创建访问Department表对象的抽象的工厂接口

 public interface IFactory{public IUser createUser();public IDepartment createDepartment();}

SqlServerFactory类:实现IFactory接口,并实例化SqlserverUser和SqlserverDepartment.

  public class SqlserverFactory implements IFactory{@Overridepublic IUser createUser() {return new SqlserverUser();}@Overridepublic IDepartment createDepartment() {return new SqlserverDepartment();}}

AccessFactory类:实现IFactory接口,并实例化AccessFactory和AccessDepartment

  public class SqlserverFactory implements IFactory{@Overridepublic IUser createUser() {return new SqlserverUser();}@Overridepublic IDepartment createDepartment() {return new SqlserverDepartment();}}

客户端代码:

  public static void main(String[] args) {User user = new User();Department department = new Department();IFactory factory = new SqlserverFactory();IUser iu = factory.createUser();iu.insert(user);iu.getUser(1);IDepartment department1 = factory.createDepartment();department1.insert(department);department1.getDepartment(1);}

结果显示:
在SQL Server中给User表增加一条记录
在SQL Server中根据用户ID得到User表一条记录
在SQL Server中给Department表添加一条数据
在SQL server中根据部门id得到Department表一条记录

这样的话,就可以做到,只需更改IFactory factory = new SqlServerFactory()为IFactory factory = new AccessFactory(),就实现了数据库访问的切换了。

这里我们已经通过需求的不断演化,重构除了一个非常重要的设计模式。现在我们数据库中有很多表,而SQL server与Access又是两大不同的分类,所以解决这种多个产品系列的问题,有一个专门的工厂模式叫抽象工厂模式

抽象工厂模式

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

抽象工厂模式UML结构图
在这里插入图片描述

AbstractProductA和AbstarctProductB是两个抽象产品 ,之所以抽象,是因为它们都有可能有两种不同的实现,就刚才的例子来说就是User和Department,而ProductA1、ProductA2和ProductB1、ProductB2就是对两个抽象产品的具体实现, 比如ProductA1可以理解为是SqlserverUser,而ProductB是SqlserverDepartment。

也就是说IFactory是一个抽象工厂接口,它里面应该包含所有的产品创建的抽象方法。而ConcreteFactory1和ConcreteFactory2就是具体的工厂了。 就像SqlserverFactory和AccessFactory一样。通常是在运行时刻再创建一个ConcreteFactory类的实例,这个具体的工厂再创建具有特定实现的产品对象,也就是说,为创建不同的产品对象,客户端应使用不同的具体工厂

抽象工厂模式的优点与缺点

抽象工厂这样做的最大好处就是易于交换产品系列由于具体工厂类,例如IFactory factory = new AccessFactory(),在一个应用中只需要在初始化的时候 出现一次,这就使得改变一个应用的具体工厂变得非常容易,它只需要改变具体工厂即可使用不同的产品配置。 我们的设计不能去防止需求的更改,那么我们的理想便是让改动变得最小,现在如果你要更改数据库访问,我们只需更改具体工厂就可以做到。**第二大好处是,**它让具体的创建实例过程与客户端分离,不会出现在客户代码中。

抽象工厂模式虽然可以很方便的切换两个数据库访问的代码,但是如果你的需求来自增加功能,比如我们现在要增加项目表Project,我们至少需要增加三个类,IProject、SqlserverProject、AccessProject,还需要更改IFactory、SqlserverFactory和AccessFactory才可以完全实现, 这步骤十分繁琐。另外,就是我们客户端程序类显然不会是只有一个,有很多地方都在使用IUser或IDepartment,而这样的设计,其实在每一个类的开始都需要声明IFactory factory = new SqlserverFactory(),如果我们需要有100个调用数据库访问的类,是不是就要更改100次IFactory factory = new AccessFactory(),显然这么做会很蠢,大批量的改动,是不符合编程这门艺术的。那么我们要如何修改呢?

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

代码示例如下:

 public class DataAccess{private static String db = "Sqlserver"; //数据库名称可以替换为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 static void main(String[] args) {User user = new User();Department department = new Department();IUser iu = DataAccess.createUser();iu.insert(user);iu.getUser(1);IDepartment idept = DataAccess.createDepartment();idept.insert(department);idept.getDepartment(2);}

上述代码中,我们直接抛弃了IFactory、SqlserverFactory和AccessFactory三个工厂类, 取而代之用DataAccess类,事先声明db值,所以简单工厂的方法都不需要参数,这样在客户端就只需要DataAccess.createUser()和DataAccess.createDepartment()来生成具体的数据库访问类实例,客户端没有出现任何一个SQL server或Access的字样,达到了解耦的目的。

但这里如果我们要增加一个新的数据库访问Oracle,抽象工厂只需要增加OracleFactory工厂类就可以了,但如果这样的话,就只能在DataAccess类中每个方法的switch加Case了。

用反射 + 抽象工厂的数据访问程序

我们要考虑的就是可不可以不在程序里写明如果是Sqlserver就去实例化SQLServer数据库相关类,如果是Access就去实例化Access相关类这样的语句,而是根据字符串db的值去某个地方找应该要实例化的类是哪一个。

这就是我们说了一种编程方式: 依赖注入。关键如何用这种方法来解决我们的switch问题,本来依赖注入是需要专门的Ioc容器提供,比如Spring,显然当前这个程序不需要这么麻烦,我们需要了解Java技术–反射。

//反射格式
Object result = Class.forName("包名.类名").getDeclaredConstructor().newInstance()

有了反射我们获得实例可以用以下两种写法

//常规写法
IUser result= new SqlserverUser();
//反射写法
IUser result = (IUser)Class.forName("chouxianggongchang.SqlserverUser").getDeclaredCOnstructor().newInstance();

实例化的效果是一样的,但常规方法写明了要实例化SqlserverUser对象,反射的写法,在包名.类名中用的是字符串,可以用变量来处理,也就可以根据需求更换。
我们可以用DataAccess类,用反射技术,取代IFactory、SqlserverFactory和AccessFactory。

   public class DataAccess{private static String assemblyName  = "chouxianggongchang.SqlserverUser"; //数据库名称可以替换为Accessprivate static String db = "Sqlserver"; //数据库名称可以替换为//创建用户对象工厂public static IUser createUser(){return (IUser) getInstance(assemblyName + db + "User");}//创建部门对象工厂public static IDepartment createDepartment(){return (IDepartment)getInstance(assemblyName + db +"Department");}public static Object getIntance(String className){Object result = null;try{result = Class.forName(className).getDeclaredConstructor().newInstance();}catch  (ClassNotFoundException e) {throw new RuntimeException(e);} catch (InvocationTargetException e) {throw new RuntimeException(e);} catch (InstantiationException e) {throw new RuntimeException(e);} catch (IllegalAccessException e) {throw new RuntimeException(e);} catch (NoSuchMethodException e) {throw new RuntimeException(e);}return result;}}

现在如果我们增加了Oracle数据访问,相关的类增加是不可避免的,这点我们无法改变,不过这叫扩展,开放 --封闭原则告诉我们,对于扩展,我们开放,但对于修改,我们要尽量关闭,就目前而言,我么只需要更改private static String db = “Sqlserver”;
为private static String db = “Oracle”;也就是

return (IUser)getIntance("chouxianggongchang" + "Sqlserver" + "User");return (IUser)getIntance("chouxianggongchang" + "Oracle" + "User");

这样的结果就是DataAccess.createUser()本来得到的是SqlserverUser()的实例,而现在变成OraclaUser的实例了。

当我们增加Project的产品时,我们可以增加三个与Project相关的类,再修改DataAccess,在其中增加一个public static IProject createProject()方法就可以了。现在我们在更改数据库访问时,只需去更改db这个字符串的值就可以了。

用反射 + 配置文件实现数据访问程序

添加一个db.properties文件,内容如下:

db=Sqlserver

再更改DataAccess类,添加与读取文件内容相关的包

import  java.io.BufferedReader;
import java.io.FileReaderl
import java.io.IOException;
import java.util.properties;public class DataAccess{private static String assemblyName  = "chouxianggongchang.SqlserverUser";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("chouxianggongchang") + "/db.properties";BufferReader bufferedReader  =new BufferReader(new FileReader(path));properties.load(bufferReader);result = properties.getProperty("db");}catch(IOException e){e.printStackTrace();}return result;}

将来要更新数据库,无须重新编译任何代码,只需要更改配置文件就可以了
我们应用了反射 + 抽象工厂模式 解决了数据库访问时的可维护可扩展问题。

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

总结

以上就是本文全部内容,本文主要向大家介绍了设计模式中的抽象工厂模式,通过更换数据库的例子介入,介绍抽象工厂模式,接着介绍了反射相关概念,以及反射和抽象工厂模式的结果运用! 感谢各位能够看到最后,如有问题,欢迎各位大佬在评论区指正,希望大家可以有所收获!创作不易,希望大家多多支持!


文章转载自:

http://mK1Yi6WC.qwdqq.cn
http://uWKd7Mhf.qwdqq.cn
http://A9gEbM11.qwdqq.cn
http://LJ2ZemgZ.qwdqq.cn
http://lptIK5Mi.qwdqq.cn
http://ohOc4o4Y.qwdqq.cn
http://Ce47d5MP.qwdqq.cn
http://cHKfs7I7.qwdqq.cn
http://eMcKG4it.qwdqq.cn
http://Xyn51U6F.qwdqq.cn
http://yZqijjgi.qwdqq.cn
http://sOpqcknJ.qwdqq.cn
http://NowUDF1x.qwdqq.cn
http://uMZBdEYN.qwdqq.cn
http://88gxv17m.qwdqq.cn
http://tCXeCbrJ.qwdqq.cn
http://NPaLq0C2.qwdqq.cn
http://hNbr9XEh.qwdqq.cn
http://ydK9wyX3.qwdqq.cn
http://k98kOV5z.qwdqq.cn
http://77xL09ws.qwdqq.cn
http://3SkUcPcc.qwdqq.cn
http://5aTySRXl.qwdqq.cn
http://IsaAPPVr.qwdqq.cn
http://7iisf62l.qwdqq.cn
http://ndvMok3q.qwdqq.cn
http://b9sF28zL.qwdqq.cn
http://gg2rHlGW.qwdqq.cn
http://jwvfDsOp.qwdqq.cn
http://YjbI8tNz.qwdqq.cn
http://www.dtcms.com/a/377975.html

相关文章:

  • LeetCode 刷题【72. 编辑距离】
  • gitlab流水线与k8s集群的联通
  • 关于神经网络中回归的概念
  • 前后端接口调试提效:Postman + Mock Server 的工作流
  • Cesium---1.133版本不修改源码支持arcgis MapServer 4490切片
  • express 框架基础和 EJS 模板
  • 多楼层室内定位可视化 Demo(A*路径避障)
  • python将pdf转txt,并切割ai
  • 可视化图解算法60: 矩阵最长递增路径
  • 4、幽络源微服务项目实战:后端公共模块创建与引入多租户模块
  • 用Next.js 构建一个简单的 CRUD 应用:集成 API 路由和数据获取
  • 如何通过url打开本地文件文件夹
  • Swagger隐藏入参中属性字段
  • JavaEE--8.网络编程
  • linux系统搭建nacos集群,并通过nginx实现负载均衡
  • 论文阅读:openai 2025 Why Language Models Hallucinate
  • Rail开发日志_9
  • opencv特征检测
  • 科普:环境隔离的工具:虚拟环境与容器Docker
  • 小迪安全v2023学习笔记(八十一讲)—— 框架安全ThinkPHPLaravelStruts2SpringBootCVE复现
  • ubuntu22.04 安装Docker
  • OpenCV 开发 -- 图像阈值处理
  • [Ubuntu][mount]ubuntu电脑挂载新硬盘
  • Maven中optional的作用
  • 使用pdfjs-dist 预览pdf,并添加文本层的实现
  • 操作系统应用开发(五)智能浏览器开发——东方仙盟元婴期
  • 蓝桥杯算法之基础知识(7)---排序题的快排和归并排序
  • leetcode-python-2154将找到的值乘以 2
  • Nginx 实战系列(十)—— LVS+Keepalived 高可用集群技术详解
  • C++ 前缀积 高频笔试考点 实用技巧 力扣 238.除自身以外数组的乘积 题解 每日一题