【设计模式之美】【建造型】工厂模式实战:如何设计一个DI框架;梳理流程,通过面向接口解耦对象创建

文章目录

  • 一. 工厂模式和 DI 容器有何区别?
  • 二. DI 容器的核心功能有哪些?
    • 1. 配置解析:解耦对象创建
    • 2. 对象创建
    • 3. 对象的生命周期管理
  • 三. 如何实现一个简单的 DI 容器?
    • 1. 最小原型设计:流程梳理
    • 2. 提供执行入口:入口的解耦
    • 3. 配置文件解析:加载配置到对象中
    • 4. 核心工厂类设计:利用反射动态创建对象

设计模式小思考:
梳理清楚业务逻辑之后,比如主流程是什么,流程中第一步、第二步…等每一步都封装到方法、某些步骤需要根据业务有不同的实现,这时使用面向接口编程来解耦对象实现。

通过本文的学习你能够了解DI容器的设计思路,并通过学习它的抽象逻辑,进一步了解面向接口的编程思路

一. 工厂模式和 DI 容器有何区别?

设计思想

实际上,DI 容器底层最基本的设计思路就是基于工厂模式的。DI 容器相当于一个大的工厂类,负责在程序启动的时候,根据配置(要创建哪些类对象,每个类对象的创建需要依赖哪些其他类对象)事先创建好对象。当应用程序需要使用某个类对象的时候,直接从容器中获取即可。正是因为它持有一堆对象,所以这个框架才被称为“容器”。

对象规模

DI 容器相对于我们上节课讲的工厂模式的例子来说,它处理的是更大的对象创建工程。上节课讲的工厂模式中,一个工厂类只负责某个类对象或者某一组相关类对象(继承自同一抽象类或者接口的子类)的创建,而 DI 容器负责的是整个应用中所有类对象的创建。

负责的工作

除此之外,DI 容器负责的事情要比单纯的工厂模式要多。比如,它还包括配置的解析、对象生命周期的管理。接下来,我们就详细讲讲,一个简单的 DI 容器应该包含哪些核心功能。

 

二. DI 容器的核心功能有哪些?

总结一下,一个简单的 DI 容器的核心功能一般有三个:配置解析、对象创建和对象生命周期管理。

1. 配置解析:解耦对象创建

作为一个通用的框架来说,框架代码跟应用代码应该是高度解耦的,DI 容器事先并不知道应用会创建哪些对象,不可能把某个应用要创建的对象写死在框架代码中。

我们可以将需要由 DI 容器来创建的类对象和创建类对象的必要信息(使用哪个构造函数以及对应的构造函数参数都是什么等等),放到配置文件中。容器读取配置文件,根据配置文件提供的信息来创建对象。

下面是一个典型的 Spring 容器的配置文件。
Spring 容器读取这个配置文件,解析出要创建的两个对象:rateLimiter 和 redisCounter,并且得到两者的依赖关系:rateLimiter 依赖 redisCounter。

public class RateLimiter {private RedisCounter redisCounter;public RateLimiter(RedisCounter redisCounter) {this.redisCounter = redisCounter;}public void test() {System.out.println("Hello World!");}//...
}public class RedisCounter {private String ipAddress;private int port;public RedisCounter(String ipAddress, int port) {this.ipAddress = ipAddress;this.port = port;}//...
}配置文件beans.xml:
<beans><bean id="rateLimiter" class="com.xzg.RateLimiter"><constructor-arg ref="redisCounter"/></bean><bean id="redisCounter" class="com.xzg.redisCounter"><constructor-arg type="String" value="127.0.0.1"><constructor-arg type="int" value=1234></bean>
</beans>

 

2. 对象创建

  1. 对于对象创建,我们提供一个工厂类,将所有对象的创建都放到这一个工厂类中,比如 BeansFactory。

  2. 代码线性膨胀问题:如果要创建的对象非常多,我们可以使用反射机制,在程序运行的过程中,动态的加载类、创建对象,不需要在代码中写死要创建哪些对象。所以,不管是创建一个对象还是十个对象,BeansFactory
    工厂类代码都是一样的。

 

3. 对象的生命周期管理

生命周期大致我们可以做如下事情:

  1. 根据scope来确定每次返回新的对象还是事先建立好的(单例)对象。 scope=prototype 表示返回新创建的对象,scope=singleton 表示返回单例对象。

  2. 配置对象是否支持懒加载。如果 lazy-init=true,对象在真正被使用到的时候才被被创建。

  3. 创建后和销毁前。我们还可以配置对象的 init-method 和 destroy-method 方法,比如 init-method=loadProperties(),destroy-method=updateConfigFile()。

  • DI 容器在创建好对象之后,会主动调用 init-method 属性指定的方法来初始化对象。
  • 在对象被最终销毁之前,DI 容器会主动调用 destroy-method 属性指定的方法来做一些清理工作,比如释放数据库连接池、关闭文件。

 

三. 如何实现一个简单的 DI 容器?

核心逻辑只需要包括这样两个部分:配置文件解析、根据配置文件通过“反射”语法来创建对象。

1. 最小原型设计:流程梳理

配置文件beans.xml
<beans><bean id="rateLimiter" class="com.xzg.RateLimiter"><constructor-arg ref="redisCounter"/></bean><bean id="redisCounter" class="com.xzg.redisCounter" scope="singleton" lazy-init="true"><constructor-arg type="String" value="127.0.0.1"><constructor-arg type="int" value=1234></bean>
</bean>

最小原型的使用方式跟 Spring 框架非常类似,示例代码如下所示:

public class Demo {public static void main(String[] args) {ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");RateLimiter rateLimiter = (RateLimiter) applicationContext.getBean("rateLimiter");rateLimiter.test();//...}
}

 

2. 提供执行入口:入口的解耦

通过刚刚的最小原型使用示例代码,我们可以看出,执行入口主要包含两部分:ApplicationContext 和 ClassPathXmlApplicationContext。其中,ApplicationContext 是接口,ClassPathXmlApplicationContext 是接口的实现类。两个类具体实现如下所示:

public interface ApplicationContext {Object getBean(String beanId);
}public class ClassPathXmlApplicationContext implements ApplicationContext {private BeansFactory beansFactory;private BeanConfigParser beanConfigParser;public ClassPathXmlApplicationContext(String configLocation) {this.beansFactory = new BeansFactory();this.beanConfigParser = new XmlBeanConfigParser();loadBeanDefinitions(configLocation);}private void loadBeanDefinitions(String configLocation) {InputStream in = null;try {// 加载配置文件中涉及到的类,并解析in = this.getClass().getResourceAsStream("/" + configLocation);if (in == null) {throw new RuntimeException("Can not find config file: " + configLocation);}List<BeanDefinition> beanDefinitions = beanConfigParser.parse(in);// 添加类到beansFactory,以便调用gebean获取实例beansFactory.addBeanDefinitions(beanDefinitions);} finally {if (in != null) {try {in.close();} catch (IOException e) {// TODO: log error}}}}@Overridepublic Object getBean(String beanId) {return beansFactory.getBean(beanId);}
}

代码流程:从 classpath 中加载 XML 格式的配置文件,通过 BeanConfigParser 解析为统一的 BeanDefinition 格式,然后,BeansFactory 根据 BeanDefinition 来创建对象。

 

3. 配置文件解析:加载配置到对象中

配置文件解析主要是 BeanConfigParser 接口负责将配置文件解析为 BeanDefinition 结构,以便 BeansFactory 根据这个结构来创建对象。

具体的代码框架如下所示:

public interface BeanConfigParser {List<BeanDefinition> parse(InputStream inputStream);List<BeanDefinition> parse(String configContent);
}public class XmlBeanConfigParser implements BeanConfigParser {@Overridepublic List<BeanDefinition> parse(InputStream inputStream) {String content = null;// TODO:...return parse(content);}@Overridepublic List<BeanDefinition> parse(String configContent) {List<BeanDefinition> beanDefinitions = new ArrayList<>();// TODO:...return beanDefinitions;}}public class BeanDefinition {private String id;private String className;private List<ConstructorArg> constructorArgs = new ArrayList<>();private Scope scope = Scope.SINGLETON;private boolean lazyInit = false;// 省略必要的getter/setter/constructorspublic boolean isSingleton() {return scope.equals(Scope.SINGLETON);}public static enum Scope {SINGLETON,PROTOTYPE}public static class ConstructorArg {private boolean isRef;private Class type;private Object arg;// 省略必要的getter/setter/constructors}
}

 

4. 核心工厂类设计:利用反射动态创建对象

BeansFactory负责根据从配置文件解析得到的 BeanDefinition 来创建对象。

  • 如果对象的 scope 属性是 singleton,那对象创建之后会缓存 map 中,下次再请求此对象的时候,直接从 map 中取出返回。
  • 如果对象的 scope 属性是 prototype,那每次请求对象,BeansFactory 都会创建一个新的对象返回。

BeansFactory 创建对象用到的主要技术点就是 Java 中的反射语法:一种动态加载类和创建对象的机制。

因为此时对象的创建是放到配置文件中,我们需要在程序运行期间,动态地根据配置文件来加载类、创建对象,所以我们可以利用 Java 提供的反射语法自己去编写代码。

 

具体代码实现如下所示:

public class BeansFactory {private ConcurrentHashMap<String, Object> singletonObjects = new ConcurrentHashMap<>();private ConcurrentHashMap<String, BeanDefinition> beanDefinitions = new ConcurrentHashMap<>();public void addBeanDefinitions(List<BeanDefinition> beanDefinitionList) {for (BeanDefinition beanDefinition : beanDefinitionList) {this.beanDefinitions.putIfAbsent(beanDefinition.getId(), beanDefinition);}for (BeanDefinition beanDefinition : beanDefinitionList) {if (beanDefinition.isLazyInit() == false && beanDefinition.isSingleton()) {createBean(beanDefinition);}}}public Object getBean(String beanId) {BeanDefinition beanDefinition = beanDefinitions.get(beanId);if (beanDefinition == null) {throw new NoSuchBeanDefinitionException("Bean is not defined: " + beanId);}return createBean(beanDefinition);}//根据从xml文件中解析到的beanDefinition,利用反射创建对象。@VisibleForTestingprotected Object createBean(BeanDefinition beanDefinition) {//1. 单例并且包含则直接返回if (beanDefinition.isSingleton() && singletonObjects.contains(beanDefinition.getId())) {return singletonObjects.get(beanDefinition.getId());}//2. 利用反射创建对象Object bean = null;try {Class beanClass = Class.forName(beanDefinition.getClassName());List<BeanDefinition.ConstructorArg> args = beanDefinition.getConstructorArgs();if (args.isEmpty()) {bean = beanClass.newInstance();} else {Class[] argClasses = new Class[args.size()];Object[] argObjects = new Object[args.size()];for (int i = 0; i < args.size(); ++i) {BeanDefinition.ConstructorArg arg = args.get(i);if (!arg.getIsRef()) {argClasses[i] = arg.getType();argObjects[i] = arg.getArg();} else {BeanDefinition refBeanDefinition = beanDefinitions.get(arg.getArg());if (refBeanDefinition == null) {throw new NoSuchBeanDefinitionException("Bean is not defined: " + arg.getArg());}argClasses[i] = Class.forName(refBeanDefinition.getClassName());argObjects[i] = createBean(refBeanDefinition);}}bean = beanClass.getConstructor(argClasses).newInstance(argObjects);}} catch (ClassNotFoundException | IllegalAccessException| InstantiationException | NoSuchMethodException | InvocationTargetException e) {throw new BeanCreationFailureException("", e);}//3. 再次判断是否是单例,并始终从map中获取实例if (bean != null && beanDefinition.isSingleton()) {singletonObjects.putIfAbsent(beanDefinition.getId(), bean);return singletonObjects.get(beanDefinition.getId());}return bean;}
}

 
参考:王争–《设计模式之美》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/46505.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

GESP C++ 三级真题(2023年9月)T1 ⼩ 杨储蓄

1、 ⼩ 杨储蓄 问题描述 ⼩ 杨共有 N个储蓄罐&#xff0c;编号从0到N-1。从第1天开始&#xff0c; ⼩杨每天都会往存钱罐里 存钱。具体来说&#xff0c;第i天他会挑选一个存钱罐 ɑ i &#xff0c;并存 ⼊i元钱。过了D天后&#xff0c;他 已经忘记每个储蓄罐里都存了多少钱了&a…

git列出提交记录的文件路径

一、如何列出某次提交记录中修改过/新增的文件&#xff1f; 方法1&#xff1a;使用 git diff-tree 命令来查看某个提交记录中修改过/新增的文件。具体来说&#xff0c;你可以使用以下命令&#xff1a; git diff-tree --no-commit-id --name-only -r <commit-hash>命令解…

C++20中的constinit说明符

constinit说明符断言(assert)变量具有静态初始化&#xff0c;即零初始化和常量初始化(zero initialization and constant initialization)&#xff0c;否则程序格式不正确(program is ill-formed)。 constinit说明符声明具有静态或线程存储持续时间(thread storage duration)的…

机器人及其相关工科专业课程体系

机器人及其相关工科专业课程体系 前言传统工科专业机械工程自动化/控制工程计算机科学与技术 新兴工科专业智能制造人工智能机器人工程 总结Reference: 前言 机器人工程专业是一个多领域交叉的前沿学科&#xff0c;涉及自然科学、工程技术、社会科学、人文科学等相关学科的理论…

ozon俄罗斯ceo丨ozon平台数据分析选品神器

ozon俄罗斯ceo是玛依妮加文特。‌作为俄罗斯最大的电子商务公司Ozon Holdings的女首席执行官&#xff0c;‌玛依妮加文特被称为俄罗斯的杰夫贝索斯&#xff08;‌亚马逊CEO&#xff09;‌。‌她在公司中发挥着重要作用&#xff0c;‌不仅负责公司的日常运营和管理&#xff0c;‌…

修改表格颜色

el-table修改表头、列的背景颜色、字体样式_el-table-column背景颜色-CSDN博客 设置表头背景颜色&#xff0c;字体 <el-table :header-cell-style"rowClass" border :data"tableDataTwo" style"width: 100%"><el-table-column width&q…

数据结构初阶(C语言)-顺序表

一&#xff0c;线性表 在进行顺序表的介绍之前&#xff0c;我们先来了解下什么是线性表&#xff1a; 线性表是n个具有相同特性的数据元素的有限序列。 线性表是⼀种在实际中广泛使用的数据结构&#xff0c;常见的线性表&#xff1a;顺序表、链表、栈、队列、字符串...线性表在…

2. 白盒测试

白盒测试 1. 白盒测试定义 测试软件的内部编码和基础设施&#xff0c;重点是根据预期和期望的输出检查预定义的输入。它基于应用程序的内部工作方式&#xff0c;并围绕内部结构测试。在这种类型的测试中&#xff0c;编程测试用例需要编程技巧。白盒测试的主要目标是通过软件关…

秒懂设计模式--学习笔记(9)【结构型-装饰器模式】

目录 8、装饰器模式8.1 装饰器模式&#xff08;Decorator&#xff09;8.2 装修&#xff08;举例&#xff09;8.3 化妆&#xff08;示例&#xff09;8.4 化妆品的多样化8.5 装饰器8.6 自由嵌套8.7 装饰器模式的各角色定义8.8 装饰器模式 8、装饰器模式 8.1 装饰器模式&#xff…

MySQL运维实战之ProxySQL(9.6)SQL黑名单

作者&#xff1a;俊达 利用mysql_query_rules表中的error_msg字段&#xff0c;可以实现SQL黑名单的功能。如果规则设置了error_msg&#xff0c;当SQL语句匹配这条规则时&#xff0c;proxysql会直接将error_msg的内容返回给客户端。 当遇到一些大查询严重影响数据库性能时&…

【开源项目】Rust开发复制文件夹目录结构工具

说明 由于我经常需要在多个大容量的移动硬盘中查找和新增文件&#xff0c;我希望把硬盘的目录结构放到服务器的自建网盘中&#xff0c;因此开发了这个工具&#xff0c;使得在不同硬盘之间的文件管理变得更加便捷 项目地址&#xff1a;https://github.com/VinciYan/folder_clon…

红色文化3D虚拟数字展馆搭建意义深远

在房地产与土地市场的浪潮中&#xff0c;无论是新城规划、乡村振兴&#xff0c;还是商圈建设&#xff0c;借助VR全景制作、虚拟现实和web3d开发技术打造的全链条无缝VR看房新体验。不仅极大提升了带看与成交的转化率&#xff0c;更让购房者足不出户&#xff0c;即可享受身临其境…

Linux Cgroups

Linux CGroup全称Linux Control Group&#xff0c; 是Linux内核的一个功能&#xff0c;用来限制&#xff0c;控制与分离一个进程组群的资源&#xff08;如CPU、内存、磁盘输入输出等&#xff09;。这个项目最早是由Google的工程师在2006年发起&#xff08;主要是Paul Menage和R…

能把进程和线程讲的这么透彻的,没有20年功夫还真不行【0基础也能看懂】

本篇会加入个人的所谓鱼式疯言 ❤️❤️❤️鱼式疯言:❤️❤️❤️此疯言非彼疯言 而是理解过并总结出来通俗易懂的大白话, 小编会尽可能的在每个概念后插入鱼式疯言,帮助大家理解的. &#x1f92d;&#x1f92d;&#x1f92d;可能说的不是那么严谨.但小编初心是能让更多人…

【verilog】Verilog 中的 ifdef 语法指南:Verilog-2001 与 SystemVerilog

目录 简介 Verilog-2001 中的 ifdef 基本语法 示例 1&#xff1a;Verilog-2001 风格 SystemVerilog 中的 ifdef 基本语法 示例 2&#xff1a;SystemVerilog 风格 示例 3&#xff1a;调试与发布配置 注意事项 简介 在 Verilog 硬件描述语言中&#xff0c;ifdef 是一种…

SQL Server 创建用户并授权

创建用户前需要有一个数据库&#xff0c;创建数据库命令如下&#xff1a; CREATE DATABASE [数据库名称]; CREATE DATABASE database1; 一、创建登录用户 方式1&#xff1a;SQL命令 命令格式&#xff1a;CREATE LOGIN [用户名] WITH PASSWORD 密码; 例如&#xff0c;创建…

Vue项目中禁用ESLint的几种常见方法

1. 通过 vue.config.js 禁用 这是最直接且推荐的方式&#xff0c;因为它直接在Vue CLI的配置中禁用ESLint。通过在项目根目录下创建或修改 vue.config.js 文件&#xff0c;并设置 lintOnSave 为 false&#xff0c;可以彻底禁用保存时的ESLint检查。 // vue.config.js module…

FlinkErr:org/apache/hadoop/hive/ql/parse/SemanticException

在flink项目中跑 上面这段代码出现如下这个异常&#xff0c; java.lang.NoClassDefFoundError: org/apache/thrift/TException 加上下面这个依赖后不报错 <dependency> <groupId>org.apache.thrift</groupId> <artifactId>libthrift</artifactId…

Python绘制相关系数热力图

相关系数热力图是一种可视化工具&#xff0c;用于展示变量之间的相关性。它在数据分析和统计中非常有用&#xff0c;尤其是在探索数据集的内在关系时。本文将介绍如何使用Python绘制相关系数热力图。 一、准备——导入库 使用Pandas来处理数据&#xff0c;Matplotlib和Seabor…

力扣第233题“数字1的个数”

在本篇文章中&#xff0c;我们将详细解读力扣第233题“数字1的个数”。通过学习本篇文章&#xff0c;读者将掌握如何计算从1到n的数字中出现的“1”的个数&#xff0c;并了解相关的复杂度分析和模拟面试问答。每种方法都将配以详细的解释&#xff0c;以便于理解。 问题描述 力…