Thanks to java-design-patterns.
设计原则
Generic
KISS
Keep is simple and stupid
YAGNI
you aren’t gonna need it
Keep things DRY
千万别让太多的 复制
充斥在你的代码中,那意味着维护噩梦、糟糕的分解。
Minimise Coupling
别耦合在一起,你们可以分开。
Law of Demeter(迪米特法则)
对象和对象之间关联应该尽量少,避免千丝万缕的关系。尽量降低类与类之间的耦合,以防止一个类发生改变,另一个依赖的类也受到影响。 1)只同你直接的朋友们通信 2)不要跟陌生人说话 3)每一个软件单位对其他的单位都只有最少的了解,这些了解仅局限于那些与本单位密切相关的软件单位.
S.O.L.I.D
简写 | 全拼 | 中文翻译 |
---|---|---|
SRP | The Single Responsibility Principle | 单一职责原则 |
OCP | The Open Closed Principle | 开放封闭原则 |
LSP | The Liskov Substitution Principle | 里氏替换原则 |
ISP | The Interface Segregation Principle | 接口分离原则 |
DIP | The Dependency Inversion Principle | 依赖倒置原则 |
单一职责原则-Single Responsibility Principle (SRP)
问题由来:类T负责两个不同的职责:职责P1,职责P2。当由于职责P1需求发生改变而需要修改类T时,有可能会导致原本运行正常的职责P2功能发生故障。
因此只能让一个类有且仅有一个职责。
换一句话说,如果一个类需要改变,改变它的理由永远只有一个。如果存在多个改变它的理由,就需要重新设计该类。
注意:单一职责原则不是只要求我们为类定义一个职责,而是提醒我们在一个类中尽量让类负载少的职责,从而保证对象具有高内聚与细粒度.
应用:分层架构就是单一职责的最佳体现,JavaEE框架就将系统根据职责的内聚给分为了不同层:
Presentation:表现层
Business:业务层
persistence: 持久层
Database:数据层
开闭原则-The Open-Closed Principle (OCP)
可以在不修改原有的模块(修改关闭)的基础上,能扩展其功能(扩展开放)。
-
A.对扩展开放(open):通过扩展增加功能,实现了功能上的扩展性。
-
B.对修改关闭(closed):模块内部代码不允许修改,该模块修改关闭,保证了功能上的稳定性。
里氏替换原则-Liskov Substitution Principle (LSP)
子类可以替换父类,并出现在父类能够出现的任何地方
接口分隔原则-Interface Segregation Principle (ISP)
最小接口原则,一个接口的某个方法未被使用的话,则说明该接口过胖,应该将其分割为多个功能专一的接口。 这就要求在设计接口的时候,使用多个专门的接口代替单一的胖接口
依赖倒置原则-Dependency Inversion Principle (DIP)
在高层模块和低层模块之间,引入一个抽象接口层。抽象接口层是对低层模块的抽象,低层模块继承或者实现该抽象接口。
由此,高层模块不依赖低层模块, 低层模块依赖抽象定义。如果高层依赖于底层模块,那么当底层模块修改时,所有的上层模块都将进行修改。因此我们需要进行控制反转IOC,及上层控制下层。 简单来说就是,高层建筑需要什么,底层就去实现什么,高层并不关注如何实现。。
High Level Classes(高层模块) –> Abstraction Layer(抽象接口层) –> Low Level Classes(低层模块)
模块之间的依赖是通过抽象产生,实现类之间不发生直接的依赖关系.
接口或者抽象类不依赖实现类,实现类依赖于接口或者抽象类.
“别给我们打电话,我们会给你打电话的”
设计模式
写出优雅代码的根本在于你对真实世界的合理抽象,就是在于对面向对象设计思想的理解。
隔离不变与变化
核心思想
JAVA设计模式的核心思想是六个基本原则,其那么多的设计模式都是基于这6个原则产生的一系列模板。
S.O.L.I.D+迪米特法则
而这些原则往往就设计到两个元素:接口,类,以及他们之间的关系。
- 接口:考虑 分隔原则
- 类的设计
- 对于类的本身,需要考虑 单一职责;
- 对于类的继承,要考虑 里氏替换原则 与 开闭原则,减少子类对父类的影响
- 协作关系的设计
- 在做框架设计时,接口之间发生联系,要考虑 依赖倒置原则;
- 当一个类与其他类发生调用关系时,利用中间者来转发调用关系( 迪米特法则 )
创造型模式
提供了一种在创建对象的同时,隐藏创建逻辑的方式。大大灵活了创建对象时的灵活性。
工厂模式和抽象工厂模式
用于在不同的条件下创建不同的实例,解决接口选择问题。 通过定义接口,让工厂类(将实际创建工作推迟到具体工厂类)实现工厂接口,让调用者自己决定实例化哪一个工厂类,返回的是一个抽象的工厂产品实例。
Facatory aFactory=new AFactory();//AFactory继承接口Facatory,在具体实现的工厂类中创建对象
AWord a=(AWord)aFactory.getWord();//Aword继承Word
(1)对于某个产品,调用者清楚地知道应该使用哪个具体的工厂来服务,实例化该具体工厂,生产出具体的产品来; (2)只是需要一种产品,而不想知道也不需要知道工厂是如何生产出来的,自需要知道具体对应的工厂就行。
单例模式(Singleton)
保证一个类仅有一个实例,而且会自行实例化并向整个系统提供这个实例。在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能。
对于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级对象而言,是非常可观的一笔系统开销;由于 new 操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻 GC 压力,缩短 GC 停顿时间。
单例模式一般有5种写法: 1.饿汉模式,先把单例进行实例化,获取的时候通过静态方法直接获取即可。缺点是类加载后就完成了类的实例化,浪费部分空间。 2.饱汉模式,先把单例置为null,然后通过静态方法获取单例时再进行实例化,但是可能有多线程同时进行实例化,会出现并发问题。 3.synchronized volatile实现。 4.使用静态内部类来实现,静态内部类只在被使用的时候才进行初始化,所以在内部类中进行单例的实例化,只有用到的时候才会运行实例化代码。然后外部类再通过静态方法返回静态内部类的单例即可。 5.枚举类,枚举类的底层实现其实也是内部类。枚举类确保每个类对象在全局是唯一的。所以保证它是单例,这个方法是最简单的。
饿汉式单例(立即加载)
这种方法非常简单,因为单例的实例被声明成 static 和 final 变量了,在第一次加载类到内存中时就会初始化,所以创建实例本身是线程安全的。
public class Singleton{
//类加载时就初始化
private static final Singleton instance = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return instance;
}
}
饿汉式单例在类加载初始化时就创建好一个静态的对象供外部使用,除非系统重启,这个对象不会改变,所以本身就是线程安全的。
Singleton1的唯一实例只能通过getInstance()来获取。
缺点是它不是一种懒加载模式(lazy initialization),单例会在加载类后一开始就被初始化,即使客户端没有调用 getInstance()方法。饿汉式的创建方式在一些场景中将无法使用:譬如 Singleton 实例的创建是依赖参数或者配置文件的,在 getInstance() 之前必须调用某个方法设置参数给它,那样这种单例写法就无法使用了。
懒汉式单例(延迟加载方式)
比之饿汉,是在getInstance()时,才创建对象。
// 饿汉式单例
public class Singleton {
//静止指令重排
private volatile static Singleton instance;
// 私有构造
private Singleton1() {}
// 双重检测,使所有的线程都能访问。
public static Singleton getInstance() {
if(instance==null){
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return single;
}
}
静态内部类方式
静态内部实现的单例是懒加载的且线程安全。(推荐)
只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance(只有第一次使用这个单例的实例的时候才加载,同时不会有线程安全问题)。
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
这种写法仍然使用JVM本身机制保证了线程安全问题;由于 SingletonHolder 是私有的,除了 getInstance() 之外没有办法访问它,因此它是懒汉式的;同时读取实例的时候不会进行同步,没有性能缺陷;也不依赖 JDK 版本。
枚举方式
public enum Singleton {
INSTANCE;
public void anyOtherMethod() {}
}
我们可以通过EasySingleton.INSTANCE来访问实例,这比调用getInstance()方法简单多了。 创建枚举默认就是线程安全的,所以不需要担心double checked locking,而且还能防止反序列化导致重新创建新的对象。但是还是很少看到有人这样写,可能是因为不太熟悉吧。
总结
就我个人而言,一般情况下直接使用饿汉式就好了,如果明确要求要懒加载(lazy initialization)会倾向于使用静态内部类,如果涉及到反序列化创建对象时会试着使用枚举的方式来实现单例。
建造者模式(Builder)
将复杂的对象构建与其具体的细节分离,以链式的方法来创造对象。
类的内部包含一个类的创建静态类,每次用这个静态类再次调用类的构造函数,具体可以参考lombok的Builder实现
原型模式(Prototype)
直接创建对象代价较大,采用原型模式,用原型实例指定创建对象的种类,在通过 拷贝 原型获取新的对象。
浅Clone和深clone实现
结构性模式
这些设计模式关注类和对象的组合。
适配器模式(Adapter)
使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。把鸭包装为鸡,当鸡用。
装饰器模式(Decorator)
相比于使用继承,装饰者模式更加灵活。简单点儿说就是当我们需要修改原有的功能,但我们又不愿直接去修改原有的代码时,设计一个Decorator套在原有代码外面。其实在 JDK 中就有很多地方用到了装饰者模式,比如 InputStream家族,InputStream 类下有 FileInputStream (读取文件)、BufferedInputStream (增加缓存,使读取文件速度大大提升)等子类都在不修改InputStream 代码的情况下扩展了它的功能。
保持接口,增强性能:修饰类继承被修饰对象的抽象父类,依赖被修饰对象的实例(被修饰对象依赖注入),以实现接口扩展。 简单说,就是我对某个类不满意,我又不想修改这个类,我就利用装饰器对其装饰一下,增加一个功能。
Coffee coffee = new BitterCoffee();
coffee = new SugarDecorator(coffee);//被修饰的对象传入装饰器,实现DI
代理模式(Proxy)
为其他对象提供一种代理以控制对这个对象的访问:增加中间层(代理层),代理类与底层实现类实现共同接口,并创建底层实现类对象(底层实现类对象依赖注入代理类),以便向外界提供功能接口
Coffee coffee = new CoffeeWithSugar();//直接创建对象
外观模式(Facade)-门面模式
在客户端和复杂系统之间再加一层,这一次将调用顺序、依赖关系等处理好。即封装底层实现,隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的高层接口
门面模式(Facade Pattern),其核心就是,外部和一个子系统的通信必须通过一个统一的外观对象进行,这样使得子系统更易于使用。
桥接模式(Bridge)
两个维度独立变化,依赖方式实现抽象与实现分离:需要一个作为桥接的接口/抽象类,多个角度的实现类依赖注入到抽象类,使它们在抽象层建立一个关联关系
过滤器模式(Filter)
使用不同的标准来过滤一组对象,通过逻辑运算以解耦的方式把它们连接起来
组合模式(Composite)
用户对单个对象和组合对象的使用具有一致性的统一接口
享元模式(Flyweight)
享元工厂类控制;HashMap实现缓冲池重用现有的同类对象,如果未找到匹配的对象,则创建新对象
行为型模式
关注对象之间的通信。
策略模式(Strategy)
策略对象依赖注入到context对象,context对象根据它的策略改变而改变它的相关行为(可通过调用内部的策略对象实现相应的具体策略行为
顾名思义就是指对象具有某个行为,但是在不同的业务场景下,这个行为应该有不同的表现形式,也就是有了不同的策略。让对象能再不同的场景下对同一行为有不同的实现,这就是策略模式。
模板方法模式(Template)
将这些通用算法抽象出来,在一个抽象类中公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。
观察者模式(Observer)
一对多的依赖关系,在观察目标类里有一个 ArrayList 存放观察者们。当观察目标对象的状态发生改变,所有依赖于它的观察者都将得到通知,使这些观察者能够自动更新(即使用推送方式)
迭代子模式(Iterator)
集合中含有迭代器:分离了集合对象的遍历行为,抽象出一个迭代器类来负责,无须暴露该对象的内部表示
责任链模式(Chain of Responsibility)
拦截的类都实现统一接口,每个接收者都包含对下一个接收者的引用。将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。
命令模式(Command)
将”行为请求者”与”行为实现者”解耦:调用者依赖命令,命令依赖接收者,调用者Invoker→命令Command→接收者Receiver
备忘录模式(Memento)
通过一个备忘录类专门存储对象状态。客户通过备忘录管理类管理备忘录类。
状态模式(State)
状态对象依赖注入到context对象,context对象根据它的状态改变而改变它的相关行为(可通过调用内部的状态对象实现相应的具体行为)
访问者模式(Visitor)
访问者模式将有关行为集中到一个访问者对象中,其改变不影响系统数据结构。
中介者模式(Mediator)
对象与对象之间存在大量的关联关系,将对象之间的通信关联关系封装到一个中介类中单独处理,从而使其耦合松散,可以独立地改变它们之间的交互
解释器模式
给定一个语言,定义它的文法表示,并定义一个解释器,这个解释器使用该标识来解释语言中的句子
空对象模式
创建一个未对该类做任何实现的空对象类,该空对象类将无缝地使用在需要检查空值的地方。不要为了屏蔽null而使用空对象,应保持用null,远比用非null的值来替代“无值”要好。
JDK中使用到的设计模式
适配器模式:Array.asList()
装饰者模式:InputStream和FilterInputStream
迭代子模式:Collection的遍历
Spring中使用到的设计模式
Spring 框架中用到了哪些设计模式:
依赖注入:DI实现IOC容器,实现依赖反转。
工厂设计模式 : Spring使用工厂模式通过 BeanFactory、ApplicationContext 创建 bean 对象。
代理设计模式 : Spring AOP 功能的实现。
单例设计模式 : Spring 中的 Bean 默认都是单例的。
模板方法模式 : Spring 中 jdbcTemplate、hibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。
装饰者模式 : 我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。
观察者模式: Spring 事件驱动模型就是观察者模式很经典的一个应用。
适配器模式 :Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式适配Controller。
IOC和DI
控制反转(IOC ,Inversion of Control),它是一种解耦的思想。它的主要目的是借助Spring的IOC 容器实现 具有依赖关系的对象之间的解耦,从而降低程序的耦合度。
车依赖车身 车身依赖底盘 底盘依赖轮子。 当车需要轮子的时候,他需要逐层的一个一个的去创建对象。 我们就需要进行IOC,IOC是一种原则,寻求上层控制下层,实现的方法是依赖注入(DI)。
所谓的DI(dependency injection),就是把底层类作为参数传入上层类,实现上层类对下层类的“控制”。
实现了DI 之后,我们在创建对象的时候仍然需要大量的构造代码,因此我们需要IOC容器,我们只需要维护一个Configuration(可以是xml可以是一段代码),而不用每次初始化一辆车都要亲手去写那一大段初始化的代码。这是引入IoC Container的第一个好处。IoC Container的第二个好处是:我们在创建实例的时候不需要了解其中的细节,我们只需要提供Config,然后利用IOC容器,自顶而下寻找依赖关系,到达底层后一步步向上创建。
控制反转和依赖注入是Spring的的创建维护对象的关键。SpringIOC容器(ApplicationContext)负责创建容器。 全局配置使用java配置(如数据库、MVC配置),业务Bean由注解配置(@Component、@Service、@Repository、@Controller)
那么IOC这种原则,可以由什么模式来实现呢?DI
我们之道Spring IOC容器相当于一个工厂,当我们需要创建一个对象时,只需要提供对应的配置/注解即可,完全不用考虑对象是如何创建的。用户只关心对象的使用。 IOC 容器负责创建对象,将对象连接在一起,配置这些对象,并从创建中处理这些对象的整个生命周期,直到它们被完全销毁。
比如项目中一个指定的Service,可能依赖于几百个类作为他的底层,如果我们一个一个去new 对象
的话,这是不可能的。
如果利用IOC,我们只需要进行配置,然后直接DI,即可使用这个Service。大大增加了项目的可维护性,降低了开发难度。
DI(Dependecy Inject,依赖注入)是实现控制反转的一种设计模式,依赖注入就是将实例变量传入到一个对象中去。
Bean的创建
Spring使用了工厂模式来创建bean对象。
用户可以使用 BeanFactory
或 ApplicationContext
来创建 bean 对象。
BeanFactory
:延迟注入(使用到某个 bean 的时候才会注入),相比于BeanFactory来说会占用更少的内存,程序启动速度更快。
ApplicationContext
:在程序启动的时候,就会加载系统定义的所有bean。
我们可以从ApplicationContext.getBean(class)
中获取任意的被IOC管理的bean。
默认的singleton的Bean
singleton意味着在我们的系统中,有且仅有一个。这样的好处有:
- 对于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级对象而言,是非常可观的一笔系统开销;
- 由于 new 操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻 GC 压力,缩短 GC 停顿时间
在Spring中,Bean的默认作用域就是单例的。当然除了singleton,SpringBean还有一下的作用域:
- prototype : 每次请求都会创建一个新的 bean 实例。
- request : 每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP request内有效。
- session : 每一次HTTP请求都会产生一个新的 bean,该bean仅在当前 HTTP session 内有效。
- global-session: 全局session作用域,仅仅在基于portlet的web应用中才有意义,Spring5已经没有了。Portlet是能够生成语义代码(例如:HTML)片段的小型Java Web插件。它们基于portlet容器,可以像servlet一样处理HTTP请求。但是,与 servlet 不同,每个 portlet 都有不同的会话
具体来说Spring是通过ConcurrentHashMap
来实现单例模式。
private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
AOP
面向切面编程(AOP,Aspect-Oriented Programming),将与业务无关,但是为业务所共同调用的逻辑(如日志管理,调用统计,请求预处理后处理,权限控制等)。
常见的切面编程包括 Filter、Interceptor以及SpringBoot的Aspect。
Spring的AOP是基于动态代理的(代理,为代理的对象添加每个接口),
如果要代理的对象,实现了某个接口,那么Spring AOP会使用JDK Proxy
,去创建代理对象,
而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候Spring AOP会使用Cglib
,生成一个被代理对象的子类来作为代理,
使用AOP,可以将涉及业务的通用功能抽象出来,这样大大减少了污染业务代码的代码量。
**Template
模板方法模式是一种行为设计模式,它定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。 模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤的实现方式。
发没发现,跟抽象类很像。当然我们一般的模板也就是利用抽象类来实现的。
public abstract class Template {
//这是我们的模板方法
public final void TemplateMethod(){
PrimitiveOperation1();
PrimitiveOperation2();
PrimitiveOperation3();
}
protected void PrimitiveOperation1(){
//当前类实现
}
//被子类实现的方法
protected abstract void PrimitiveOperation2();
protected abstract void PrimitiveOperation3();
}
public class TemplateImpl extends Template {
@Override
public void PrimitiveOperation2() {
//当前类实现
}
@Override
public void PrimitiveOperation3() {
//当前类实现
}
}
Spring 中 jdbcTemplate、hibernateTemplate
等以 Template
结尾的对数据库操作的类,它们就使用到了模板模式
Spring事件驱动
当两个对象存在依赖关系的时候,即当一个对象改变,这个对象所依赖的对象也会做出相应的反应。
Spring 事件驱动模型就是观察者模式很经典的一个应用。Spring 事件驱动模型非常有用,在很多场景都可以解耦我们的代码。 比如我们每次清除班级的时候,它所在的所有学生都应该进行清除。(学生未设置外键)
事件
ApplicationContextEvent
是Spring提供的事件的角色,继承了java.util.EventObject
根据时机不同Spring默认存在了一下的事件,他们都是对ApplicationContextEvent
的实现
- ContextStartedEvent:ApplicationContext 启动后触发的事件;
- ContextStoppedEvent:ApplicationContext 停止后触发的事件;
- ContextRefreshedEvent:ApplicationContext 初始化或刷新完成后触发的事件;
- ContextClosedEvent:ApplicationContext 关闭后触发的事件。
事件监听者
ApplicationEvent
充当了事件监听者角色,它是一个接口,里面只定义了一个 onApplicationEvent()方法来处理ApplicationEvent。
ApplicationListener接口类源码如下,可以看出接口定义看出接口中的事件只要实现了 ApplicationEvent就可以了。
所以,在 Spring中我们只要实现 ApplicationListener 接口的 onApplicationEvent() 方法即可完成监听事件
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
void onApplicationEvent(E var1);
}
事件发布者
ApplicationEventPublisher
充当了事件的发布者,它也是一个接口。
@FunctionalInterface
public interface ApplicationEventPublisher {
default void publishEvent(ApplicationEvent event) {
this.publishEvent((Object)event);
}
void publishEvent(Object var1);
}
使用流程
1.定义一个事件:继承ApplicationEvent
2.定义监听者:实现ApplicationListener
3.发布消息:通过 ApplicationEventPublisher
的 publishEvent()
方法发布消息。
Example:
// 定义一个事件,继承自ApplicationEvent并且写相应的构造函数
public class DemoEvent extends ApplicationEvent{
private static final long serialVersionUID = 1L;
private String message;
public DemoEvent(Object source,String message){
super(source);
this.message = message;
}
public String getMessage() {
return message;
}
// 定义一个事件监听者,实现ApplicationListener接口,重写 onApplicationEvent() 方法;
@Component
public class DemoListener implements ApplicationListener<DemoEvent>{
//使用onApplicationEvent接收消息
@Override
public void onApplicationEvent(DemoEvent event) {
String msg = event.getMessage();
System.out.println("接收到的信息是:"+msg);
}
}
// 发布事件,可以通过ApplicationEventPublisher 的 publishEvent() 方法发布消息。
@Component
public class DemoPublisher {
@Autowired
ApplicationContext applicationContext;
public void publish(String message){
//发布事件
applicationContext.publishEvent(new DemoEvent(this, message));
}
}
适配器模式
适配器模式(Adapter Pattern) ,使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。把鸭包装为鸡,当鸡用。
SpringAOP中的适配器模式
SpringAOP是基于代理模式,但是AOP的增强或者通知(Advice).
Advice
包括了BeforeAdvice,AfterAdvice,AfterReturningAdvice
对应的包含拦截器包含MethodBeforeAdviceInterceptor、AfterReturningAdviceAdapter、AfterReturningAdviceInterceptor
Spring预定义的通知要通过对应的适配器,适配成 MethodInterceptor接口(方法拦截器)类型的对象(如:MethodBeforeAdviceInterceptor 负责适配 MethodBeforeAdvice)。
SpringMVC中的适配器模式
在Spring MVC中,DispatcherServlet 根据请求信息调用 HandlerMapping,解析请求对应的 Handler。解析到对应的 Handler(也就是我们平常说的 Controller 控制器)后,开始由HandlerAdapter 适配器处理。HandlerAdapter 作为期望接口,具体的适配器实现类用于对目标类进行适配,Controller 作为需要适配的类。
为什么要在 Spring MVC 中使用适配器模式? Spring MVC 中的 Controller 种类众多,不同类型的 Controller 通过不同的方法来对请求进行处理。如果不利用适配器模式的话,DispatcherServlet 直接获取对应类型的 Controller,需要的自行来判断,像下面这段代码一样:
if(mappedHandler.getHandler() instanceof MultiActionController){
((MultiActionController)mappedHandler.getHandler()).xxx
}else if(mappedHandler.getHandler() instanceof XXX){
...
}else if(...){
...
}
假如我们再增加一个 Controller类型就要在上面代码中再加入一行 判断语句,这种形式就使得程序难以维护,也违反了设计模式中的开闭原则 – 对扩展开放,对修改关闭。
装饰者模式
装饰者模式(Decorator)可以动态地给对象添加一些额外的属性或行为。 Spring 中配置 DataSource 的时候,DataSource 可能是不同的数据库和数据源。我们能否根据客户的需求在少修改原有类的代码下动态切换不同的数据源?这个时候就要用到装饰者模式