1 认识设计模式
1.1 设计模式简介
软件设计模式(Software Design Pattern),俗称设计模式,设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。它描述了在软件设计过程中的一些不断重复发生的问题,以及该问题的解决方案。也就是说,它是解决特定问题的一系列套路,是前辈们的代码设计经验的总结,具有一定的普遍性,可以反复使用。使用设计模式的目的是为了代码重用、让代码更容易被他人理解、保证代码可靠性。
1.2 设计原则
优良的系统设计具备特点:
- 可扩展性(Extensibility)
- 灵活性(Flexibility)
- 组件化可插拔式(Pluggability)
面向对象编程常用的设计原则包括7个,这些原则并不是孤立存在的,它们相互依赖,相互补充。
1.2.1 单一职责原则
定义:一个对象应该只包含单一的职责,并且该职责被完整地封装在一个类中。
解说:一个类(或者大到模块,小到方法)承担的职责越多,它被复用的可能性越小,而且如果一个类承担的职责过多,就相当于将这些职责耦合在一起,当其中一个职责变化时,可能会影响其他职责的运作。
类的职责主要包括两个方面:数据职责和行为职责,数据职责通过其属性来体现,而行为职责通过其方法来体现。
单一职责原则是实现高内聚、低耦合的指导方针,在很多代码重构方法中都能找到它的存在,它是最简单但又最难运用的原则,需要设计人员发现类的不同职责并将其分离,而发现类的多重职责需要设计人员具有较强的分析设计能力和相关重构经验。
实例:以登录实现为例:
原始设计方案:
使用单一职责原则对其进行重构:
1.2.2 开闭原则
定义:一个软件实体应当对扩展开放,对修改关闭。也就是说在设计一个模块的时候,应当使这个模块可以在不被修改的前提下被扩展,即实现在不修改源代码的情况下改变这个模块的行为。
解说:开闭原则还可以通过一个更加具体的“对可变性封装原则”来描述,对可变性封装原则(EVP)要求找到系统的可变因素并将其封装起来。
如果一个软件设计符合开闭原则,那么可以非常方便地对系统进行扩展,而且在扩展时无须修改现有代码,使得软件系统在拥有适应性和灵活性的同时具备较好的稳定性和延续性。为了满足开闭原则,需要对系统进行抽象化设计,抽象化是开闭原则的关键。
实例:我们拿报表功能来说, BarChart 和 PieChart 为不同的报表功能,此时在 ChartDisplay 中使用报表功能,可以直接new对应的功能,但如果增加新的报表功能,在 ChartDisplay 中使用,就需要改代码了,这就违背了开闭原则。
原始设计方案:
基于开闭原则进行重构:
1.2.3 里氏代换原则
定义:所有引用基类(父类)的地方必须能透明地使用其子类的对象。
解说:里氏代换原则可以通俗表述为:在软件中将一个基类对象替换成它的子类对象,程序将不会产生任 何错误和异常,反过来则不成立,如果一个软件实体使用的是一个子类对象的话,那么它不 一定能够使用基类对象。
里氏代换原则是实现开闭原则的重要方式之一,由于使用基类对象的地方都可以使用子类对 象,因此在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用 子类对象来替换父类对象。
实例:我们以给客户发消息为例,给VIP客户(VipCustomer)和普通客户(CommonCustomer)发消息,在SendMessage 中分别定义给普通会员和VIP发消息,如果以后有新的客户分类,不仅要添加客户分类,还要修改SendMessage ,违背了开闭原则。
原始设计方案:
基于里氏代换原则进行重构:
1.2.4 依赖倒转原则
定义:抽象不应该依赖于细节,细节应当依赖于抽象。换言之,要针对接口编程,而不是针对实现编程。
实例:我们可以把之前的开闭原则案例修改一下,利用Spring框架进行修改,可读性更强,同时遵循了开闭原则、里氏代换原则和依赖倒转原则,如下图:
1.2.5 接口隔离原则
定义:使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口。
讲解:接口仅仅提供客户端 需要的行为,客户端不需要的行为则隐藏起来,应当为客户端提供尽可能小的单独的接口, 而不要提供大的总接口。在面向对象编程语言中,实现一个接口就需要实现该接口中定义的 所有方法,因此大的总接口使用起来不一定很方便,为了使接口的职责单一,需要将大接口 中的方法根据其职责不同分别放在不同的小接口中,以确保每个接口使用起来都较为方便, 并都承担某一单一角色。接口应该尽量细化,同时接口中的方法应该尽量少,每个接口中只 包含一个客户端(如子模块或业务逻辑类)所需的方法即可,这种机制也称为“定制服务”,即 为不同的客户端提供宽窄不同的接口
实例:下图展示了一个拥有多个客户类的系统,在系统中定义了一个巨大的接口DataRead来服务所有的客户类。
原始设计方案:
、
基于接口隔离原则进行重构:
1.2.6 合成复用原则
定义:尽量使用对象组合,而不是继承来达到复用的目的
讲解:合成复用原则就是在一个新的对象里通过关联关系(包括组合关系和聚合关系)来使用一些 已有的对象,使之成为新对象的一部分;新对象通过委派调用已有对象的方法达到复用功能 的目的。简言之:复用时要尽量使用组合/聚合关系(关联关系),少用继承。
在面向对象设计中,可以通过两种方法在不同的环境中复用已有的设计和实现,即通过组合/ 聚合关系或通过继承,但首先应该考虑使用组合/聚合,组合/聚合可以使系统更加灵活,降低 类与类之间的耦合度,一个类的变化对其他类造成的影响相对较少;其次才考虑继承,在使 用继承时,需要严格遵循里氏代换原则,有效使用继承会有助于对问题的理解,降低复杂 度,而滥用继承反而会增加系统构建和维护的难度以及系统的复杂度,因此需要慎重使用继 承复用。
通过继承来进行复用的主要问题在于继承复用会破坏系统的封装性,因为继承会将基类的实 现细节暴露给子
类,由于基类的内部细节通常对子类来说是可见的,所以这种复用又称“白 箱”复用,如果基类发生改变,那么子类的实现也不得不发生改变;
由于组合或聚合关系可以将已有的对象(也可称为成员对象)纳入到新对象中,使之成为新对象的一部分,因此
新对象可以调用已有对象的功能,这样做可以使得成员对象的内部实现 细节对于新对象不可见。
实例:图书管理系统中,如果数据在MySQL中,我们需要创建一个链接MySQL的工具类 MySQLUtil ,Dao只需要继承该工具类即可操作数据库,如果把数据库换成Oracle,我们需要新建一个工具类 OracleUtil ,Dao需要修改继承对象改为 OracleUtil ,这就违反了开闭原则。
原始设计方案
基于合成复用原则进行重构:
我们把 OracleUtil 作为 MySQLUtil 的子类,BookDao中把 MySQLUtil 作为一个属性组合进来,每次需要变更数据库链接的时候,只需要修改BookDao的依赖注入配置文件即可。这里符合里氏替换原则。
1.2.7 迪米特法则
定义:一个软件实体应当尽可能少地与其他实体发生相互作用。
讲解:如果一个系统符合迪米特法则,那么当其中某一个模块发生修改时,就会尽量少地影响其他 模块,扩展会相对容易,这是对软件实体之间通信的限制,迪米特法则要求限制软件实体之 间通信的宽度和深度。迪米特法则可降低系统的耦合度,使类与类之间保持松散的耦合关系。
迪米特法则要求我们在设计系统时,应该尽量减少对象之间的交互,如果两个对象之间不必 彼此直接通信,那么这两个对象就不应当发生任何直接的相互作用,如果其中的一个对象需 要调用另一个对象的某一个方法的话,可以通过第三者转发这个调用。简言之,就是通过引 入一个合理的第三者来降低现有对象之间的耦合度。
作用:降低系统的耦合度
实例:我们在做增删改查的时候,如果直接用控制层调用Dao,业务处理的关系会比较乱,我们需要合理增加一个中间对象(业务层)来解决个问题。
原始设计方案
基于迪米特法则进行重构
1.3 设计模式分类
GOF中共提到了23种设计模式不是孤立存在的,很多模式之间存在一定的关联关系,在大的系统开发中常常同时使用多种设计模式。这23种设计模式根据功能作用来划分,可以划分为3类:
(1)创建型模式:用于描述“怎样创建对象”,它的主要特点是“将对象的创建与使用分离”,单例、原型、工厂方法、抽象工厂、建造者5种设计模式属于创建型模式。
(2)结构型模式:用于描述如何将类或对象按某种布局组成更大的结构,代理、适配器、桥接、装饰、外观、享元、组合7种设计模式属于结构型模式。
(3)行为型模式:用于描述类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,以及怎样分配职责。模板方法、策略、命令、职责链、状态、观察者、中介者、迭代器、访问者、备忘录、解释器11种设计模式属于行为型模式。
GOF的23种设计模式
1、单例(Singleton)模式:某个类只能生成一个实例,该类提供了一个全局访问点供外部获取该实例,其拓展是有限多
例模式。
2、原型(Prototype)模式:将一个对象作为原型,通过对其进行复制而克隆出多个和原型类似的新实例。
3、工厂方法(Factory Method)模式:定义一个用于创建产品的接口,由子类决定生产什么产品。
4、抽象工厂(AbstractFactory)模式:提供一个创建产品族的接口,其每个子类可以生产一系列相关的产品。
5、建造者(Builder)模式:将一个复杂对象分解成多个相对简单的部分,然后根据不同需要分别创建它们,最后构建成该
复杂对象。
6、代理(Proxy)模式:为某对象提供一种代理以控制对该对象的访问。即客户端通过代理间接地访问该对象,从而限制、
增强或修改该对象的一些特性。
7、适配器(Adapter)模式:将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的
那些类能一起工作。
8、桥接(Bridge)模式:将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽
象和实现这两个可变维度的耦合度。
9、装饰(Decorator)模式:动态的给对象增加一些职责,即增加其额外的功能。
10、外观(Facade)模式:为多个复杂的子系统提供一个一致的接口,使这些子系统更加容易被访问。
11、享元(Flyweight)模式:运用共享技术来有效地支持大量细粒度对象的复用。
12、组合(Composite)模式:将对象组合成树状层次结构,使用户对单个对象和组合对象具有一致的访问性。
13、模板方法(TemplateMethod)模式:定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以
不改变该算法结构的情况下重定义该算法的某些特定步骤。
14、策略(Strategy)模式:定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的改变不会影响
使用算法的客户。
15、命令(Command)模式:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。
16、职责链(Chain of Responsibility)模式:把请求从链中的一个对象传到下一个对象,直到请求被响应为止。通
过这种方式去除对象之间的耦合。
17、状态(State)模式:允许一个对象在其内部状态发生改变时改变其行为能力。
18、观察者(Observer)模式:多个对象间存在一对多关系,当一个对象发生改变时,把这种改变通知给其他多个对象,
从而影响其他对象的行为。
19、中介者(Mediator)模式:定义一个中介对象来简化原有对象之间的交互关系,降低系统中对象间的耦合度,使原有
对象之间不必相互了解。
20、迭代器(Iterator)模式:提供一种方法来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。
21、访问者(Visitor)模式:在不改变集合元素的前提下,为一个集合中的每个元素提供多种访问方式,即每个元素有多
个访问者对象访问。
22、备忘录(Memento)模式:在不破坏封装性的前提下,获取并保存一个对象的内部状态,以便以后恢复它。
23、解释器(Interpreter)模式:提供如何定义语言的放法,以及对语言句子的解释方法,即解释器。
2 设计模式常用案例
2.1 单例模式
单例模式(Singleton Pattern)是 JAVA 中最常见的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
单例模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。该类还提供了一种访问它唯一对象的方式,其他类可以直接访问该方法获取该对象实例,而不需要实例化该类的对象。
单例模式特点:
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。
单例模式优点:
1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例。
2、避免对资源的多重占用(比如写文件操作)
单例模式真实应用场景
1、网站的计数器
2、应用程序的日志应用
3、数据库连接池设计
4、多线程的线程池设计
2.1.1 单例模式-饿汉式
创建一个单例对象 SingleModel , SingleModel 类有它的私有构造函数和本身的一个静态实例。SingleModel 类提供了一个静态方法,供外界获取它的静态实例。 DesignTest 我们的演示类使用 SingleModel 类来获取 SingleModel 对象。
2.1.2 多种单例模式讲解
单例模式有多种创建方式,刚才创建方式没有特别的问题,但是程序启动就需要创建对象,不管你用不用到对象,都会创建对象,都会消耗一定内存。因此在单例的创建上出现了多种方式。
懒汉式:
懒汉式有这些特点:
1、延迟加载创建,也就是用到对象的时候,才会创建
2、线程安全问题需要手动处理(不添加同步方法,线程不安全,添加了同步方法,效率低)
3、实现容易
案例如下: SingleModel1
如果在创建对象实例的方法上添加同步 synchronized ,但是这种方案效率低,代码如下:
双重校验锁: SingleModel2
这种方式采用双锁机制,安全且在多线程情况下能保持高性能。
3 Spring设计模式剖析
Spring是一个分层的JavaSE/EE full-stack(一站式) 轻量级开源框架,非常受企业欢迎,他解决了业务逻辑层和其他各层的松耦合问题,它将面向接口的编程思想贯穿整个系统应用。在Spring源码中拥有多个优秀的设计模式使用场景,有非常高的学习价值。
3.1 观察者模式
定义: 对象之间存在一对多或者一对一依赖,当一个对象改变状态,依赖它的对象会收到通知并自动更新。
MQ其实就属于一种观察者模式,发布者发布信息,订阅者获取信息,订阅了就能收到信息,没订阅就收不到信息。
优点:
1、观察者和被观察者是抽象耦合的。
2、建立一套触发机制
缺点:
1、如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
2、如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
3.1.1 Spring观察者模式
ApplicationContext 事件机制是观察者设计模式的实现,通过 ApplicationEvent 类和 ApplicationListener 接口,可以实现 ApplicationContext 事件处理。
如果容器中有一个 ApplicationListener Bean ,每当 ApplicationContext 发布 ApplicationEvent 时,ApplicationListener Bean 将自动被触发。这种事件机制都必须需要程序显示的触发。
其中spring有一些内置的事件,当完成某种操作时会发出某些事件动作。比如监听 ContextRefreshedEvent 事件,当所有的bean都初始化完成并被成功装载后会触发该事件,实现ApplicationListener<ContextRefreshedEvent> 接口可以收到监听动作,然后可以写自己的逻辑。同样事件可以自定义、监听也可以自定义,完全根据自己的业务逻辑来处理。
对象说明:
1、ApplicationContext容器对象
2、ApplicationEvent事件对象(ContextRefreshedEvent容器刷新事件)
3、ApplicationListener事件监听对象
3.1.2 ApplicationContext事件监听
当ApplicationContext内的Bean对象初始化完成时,此时可以通过监听 ContextRefreshedEvent 得到通知!我们来
模拟一次。
创建监听对象:
ApplicationContextListener
将对象添加到容器中:
测试
此时会打印如下信息:
应用场景:
程序启动,初始化过程中,需要确保所有对象全部初始化完成,此时在从容器中获取指定对象做相关初始化操作。例如:将省、市、区信息初始化到缓存中。
3.1.3 自定义监听事件
自定义监听事件可以监听容器变化,同时也能精确定位指定事件对象,我们编写一个案例演示自定义监听事件实现流
程。
定义事件监听对象: MessageNotifier
定义事件对象: MessageEvent
将对象添加到容器中:
添加事件测试
测试打印结果如下:
3.2 代理模式
定义:给某对象提供一个代理对象,通过代理对象可以访问该对象的功能。主要解决通过代理去访问[不能直接访问的对象,例如租房中介,你可以直接通过中介去了解房东的房源信息,此时中介就可以称为代理。
优点:
1、职责清晰。
2、高扩展性。
3、智能化。
缺点
1、由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。
2、实现代理模式需要额外的工作,有些代理模式的实现非常复杂。
代理实现方式:
基于接口的动态代理
提供者:JDK官方的Proxy类。
要求:被代理类最少实现一个接口。
基于子类的动态代理
提供者:第三方的CGLib,如果报asmxxxx异常,需要导入asm.jar。
要求:被代理类不能用final修饰的类(最终类)。
3.2.1 JDK动态代理
JDK动态代理要点:
1、被代理的类必须实现一个接口
2、创建代理对象的时候,用JDK代理需要实现InvocationHandler
3、代理过程在invoke中实现
我们以王五租房为例,王五通过中介直接租用户主房屋,中介在这里充当代理角色,户主充当被代理角色。
创建房东接口对象: LandlordService
创建房东对象: Landlord
创建代理处理过程对象: QFangProxy
创建代理,并通过代理调用房东方法: JdkProxyTest
运行结果如下:
3.2.2 CGLib动态代理
CGLib动态代理要点:
1、代理过程可以实现MethodInterceptor(Callback)接口中的invoke来实现
2、通过Enhancer来创建代理对象
在上面的案例基础上,把 QFangProxy 换成 SFangProxy ,代码如下:
创建测试类: CGLibProxyTest ,代码如下
3.2.3 Spring AOP-动态代理
基于SpringAOP可以实现非常强大的功能,例如声明式事务、基于AOP的日志管理、基于AOP的权限管理等功能,利用AOP可以将重复的代码抽取,重复利用,节省开发时间,提升开发效率。Spring的AOP其实底层就是基于动态代理而来,并且支持JDK动态代理和CGLib动态代理,动态代理的集中体现在 DefaultAopProxyFactory 类中,我们来解析下 DefaultAopProxyFactory 类。
如果我们在spring的配置文件中不配置 <aop:config proxy-target-class="true"> ,此时默认使用的将是JDK动态代理,如果配置了,则会使用CGLib动态代理。
JDK动态代理的创建 JdkDynamicAopProxy 如下:
CGLib动态代理的创建 ObjenesisCglibAopProxy 如下:
3.3 工厂设计模式
定义:工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。它负责实现创建所有实例的内部逻辑。工厂类的创建产品类的方法可以被外界直接调用,创建所需的产品对象。
优点:
1、一个调用者想创建一个对象,只要知道其名称就可以了。
2、屏蔽产品的具体实现,调用者只关心产品的接口。
3、降低了耦合度
3.3.1 工厂模式案例
我们来做这么一个案例,创建一个接口Product和Product的实现类Mobile以及Car,再定义一个具体的工厂对象ProcutFactory,并通过ProductFactory来获取指定的Product。
Product接口:
创建接口实现类Mobile:
创建接口实现类Car:
创建工厂ProductFactory,根据参数创建指定产品对象:
使用工厂ProductFactory创建指定对象:
运行结果如下
3.3.2 BeanFactory工厂模式
Spring内部源码也有工厂模式的实现,并且解决了上面我们提到的工厂模式的缺陷问题。
Spring中的BeanFactory就是简单工厂模式的体现,根据传入一个唯一的标识来获得Bean对象,但是否是在传入参数后创建还是传入参数前创建这个要根据具体情况来定
BeanFactory源码:
在BeanFactory接口中,有多个getBean方法,该方法其实就是典型的工厂设计模式特征,在接口中定义了创建对象的方法,而对象如何创建其实在接口的实现类中实现
DefaultListableBeanFactory 。
我们用Spring的工厂对象BeanFactory来解决上面工厂模式案例所带来的问题
案例:
测试:
结果:
使用Spring的BeanFactory,以后要新增一个产品,只需要创建产品对应的xml配置即可,而不需要像ProductFactory 那样硬编码存在。
3.4 适配器模式
定义
将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作
优点:
1、可以让任何两个没有关联的类一起运行。
2、提高了类的复用。
3、灵活性好
缺点:
过多地使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。
3.4.1 Spring Aop适配器+代理模式案例
Spring架构中涉及了很多设计模式,本文来介绍下Spring中在AOP实现时Adapter模式的使用。AOP本质上是Java动态代理模式的实现和适配器模式的使用。
我们基于Spring的前置通知来实现一个打卡案例,再基于前置通知讲解前置适配模式。
创建打卡接口: PunchCard 定义打卡方法,代码如下:
定义打卡实现: PunchCardImpl 实现打卡操作,代码如下:
前置通知创建: PunchCardBefore 实现在打卡之前识别用户身份,代码如下:
spring.xml配置前置通知:
测试效果如下:
3.4.2 Spring AOP适配器体系
前置通知其实就是适配器模式之一,刚才我们编写的前置通知实现了接口 MethodBeforeAdvice 。Spring容器将每个具体的advice封装成对应的拦截器,返回给容器,这里对advice转换就需要用到适配器模式。我们来分析下适配器的实现:
如下代码实现了接口 BeforeAdvice ,而 BeforeAdvice 继承了 Advice 接口,在适配器接口 AdvisorAdapter 里面定义了方法拦截。
AdvisorAdapter :定义了2个方法,分别是判断通知类型是否匹配,如果匹配就会获取对应的方法拦截。
MethodBeforeAdviceAdapter :实现了 AdvisorAdapter ,代码如下:
刚才我们在spring.xml中配置了代理类,代理类通过
DefaultAdvisorAdapterRegistry类来注册相应的适配器,我们可以在
4 架构中的设计模式
我们结合上面所学的设计模式,开发一款框架,该框架具备Spring的功能和SpringMVC功能,这里我们会提供部分工具,直接供大家使用。开发的框架流程图如上,整体基于Servlet实现。
准备工作:
搭建一个工程,引入相关依赖包,以及工具包,pom.xml代码如下:
扩展
在 AccountController 类上有几个@RequestMapping注解,这个注解是我们自定义的,这个注解上有对应的值,我们可以通过 ParseAnnotation 工具类解析该注解,并将解析值存储到Map中,修改 BaseInit ,代码如下:
解析这个注解的作用,可以通过用于请求的路径来判断该路径归哪个对象处理;
此时不要忘了配置web.xml
4.1 自定义框架-适配器视图渲染
4.1.1 流程分析
在我们做视图解析的时候,有不同的解析方式,比如有直接输入json数据、重定向、转发、输出文件流等多种方式。通常采用哪种解析方式由执行方法的返回值决定,例如返回一个字符串,我们可以把返回的字符串当做响应的页面,这时候可以采用转发的方式,如果返回的是一个javabean,这时候我们可以采用输出json字符串的方式解析。像这一块的实现,我们可以采用适配器模式实现。
实现步骤如下:
1、定义一个视图解析接口ViewHandler,提供2种解析方式,分别为json输出和forward
2、为接口实现每种解析方式。分别创建PrintViewHandler和ForwardViewHandler
3、创建一个视图渲染接口View,View中提供渲染方法render
4、创建View的渲染实现ViewAdapter,通过提供的相应结果,来创建对应的试图解析器,并调用解析方式
4.1.2 适配器模式实现视图解析
1)创建视图解析器接口
2)创建json解析和转发
JSON解析对象: PrintViewHandler
转发解析对象: ForwardViewHandler
3)视图渲染接口和实现
视图渲染接口: View
视图渲染实现: ViewAdapter
我们创建一个类 DispacherServlet ,继承BaseInit,同时在 web.xml 中把 BaseInit 换成 DispacherServlet ,并重写 service 方法,实现拦截所有用户请求,在 service 中使用执行反射调用,然后调用刚才写好的适配器查找对应的渲染方式执行渲染,代码如下:
我们来测试一下,请求 <
http://localhost:18081/account/info>
请求 <
http://localhost:18081/account/one>
4.2 自定义框架-观察者模式
上面虽然已经实现了MVC模型对应功能,但是每次调用对象都是创建了新对象,我们可以对这里进行优化,让每次调用的对象是单例对象,这时候我们就需要初始化的时候把对象创建好了,但是对象和对象之间又存在依赖关系,我们可以在配置文件中配置这种关系。这里我们可以使用观察者模式和单例模式。
4.2.1 流程分析
我们编写一个类似Spring的MVC框架,这里采用观察者模式实现监听文件加载,实现步骤如下:
1、编写BaseInit类,并继承HttpServlet
2、重写HttpServlet中的init(ServletConfig config)方法
3、编写一个抽象类ParseFile,在该类中编写监听的对象baseInit,同时编写一个通知方法load(InputStream is)
4、编写ParseXml继承ParseFile,实现load(InputStream is)方法,当BaseInit中的init方法加载到变更文件
时,该方法将得到变更通知。
4.2.2 监听文件加载并解析文件
要创建实例的对象信息以及依赖关系信息,我们可以配置到配置文件中,配置如下:
讲解
1、配置文件中一个bean表示要创建一个对象的实例
2、id表示创建对象后的唯一id
3、class表示要创建哪个对象的全限定名
4、property表示给创建的对象指定属性赋值
5、property.name表示给指定对象的指定属性赋值
6、property.ref表示将指定对象赋值给property.name指定的属性
我们需要加载哪个配置文件进行解析,需要在web.xml中配置,代码如下:
当 BaseInit 获取到要解析的文件内容时,通知 ParseFile 解析对应的配置文件,在这里我们可以使用观察者模式,让当前 BaseInit 和 ParseFile 关联,这里 ParseFile 是一个抽象对象,给它编写一个实现 ParseXml ,以后也许还会写 ParseYaml 等。这里用到的设计模式是观察者模式,观察到要解析配置文件,通知指定对象进行解析。
创建 ParseFile ,代码如下
创建 ParseXml ,代码如下:
修改 BaseInit ,代码如下
4.3 自定义框架-工厂模式
此时我们通知 ParseXml 加载需要解析的配置文件,这时候我们需要创建一个工厂对象来实现对象创建和对象获取,这块我们可以采用工厂模式实现
4.3.1 流程分析
工厂的流程如上图,工厂需要先解析 spring.xml ,将解析的实体bean存储起来,用户每次获取对应的实例时,可以通过请求uri获取,也可以通过 spring.xml 中唯一的id获取
4.3.2 工厂模式实现获取对象实例
1)创建工厂接口
创建工厂接口 BeanFactory ,因为以后可能会有注解实现方式,所以这里为工厂创建一个接口,代码如下:
2)工厂实现
创建工厂实现类 XmlBeanFactory ,同时实现根据url和根据id获取对应实例方法,代码如下:
工厂测试:
3)重写service反射调用
为了每次能使用单例对象处理用户请求从而更节省资源,我们需要根据用户的uri找到对应的实例。
我们可以按照这个步骤实现通过uri找到对应的实例:
1、循环所有methods,获取每个method对应的uri和method对应的Class
2、匹配beans中实例的Class和method的Class相同的对象,获取对应的key(也就是id)
3、把uri和id重新组合成一个新的Map,叫urlIdMaps
4、用户每次请求的时候,直接通过uri从urlIdMaps中获取对应实例
这个操作,该
ParseAnnotation.parseUrlMappingInstance(methods,beans) 方法已经帮我们实现了,所以我们只需要把实现的整体流程搬到 XmlBeanFactory 中就可以了。
修改 XmlBeanFactory ,代码如下:
4.4 自定义框架-代理模式增强
4.4.1 流程分析
我们模拟Spring的声明式事务控制,在业务层进行增强,我们可以按照如下步骤实现:
1、创建增强类TransactionManager,在里面编写一个增强方法begin
2、创建一个BeanProxy对象,用于给指定包下的对象创建代理
3、每次ParseXml加载解析之后,调用BeanProxy给指定包下的对象创建代理
4.4.2 业务层代理模式增强
1)指定增强位置
我们首先在spring.xml配置文件中配置一下增强的位置,before表示前置增强, package 表示指定包下的对象进行前
置增强, ref 表示指定增强的类, method 表示增强的类中指定的方法。
我们需要创建一个增强类 TransactionManager :
2)增强实现
我们接着需要对增强进行实现,我们可以按照如下步骤实现:
1、解析xml,需要获取增强的包,实现增强的类的方法
2、获取所有解析的实例,并判断每个实例是否是该包下的对象
3、如果是该包下的对象,给它创建代理,执行增强
4、将当前对象替换成代理对象
5、将当前对象下的引用属性替换成代理
我们创建该代理增强类 BeforeProxyBean ,代码如下:
创建代理类 ProxyBeanFactory ,代码如下:
在工厂 XmlBeanFactory 实现调用即可:
运行测试结果如下:
若有收获,就点个赞吧