具体地址:http://www.yiyehu.tech/archives/2020/06/01/design-pattern-in-spring
声明:大纲与部分内容来自于 https://www.runoob.com/design-pattern/design-pattern-intro.html
Contents [hide]
设计模式简介
重用代码、让代码更容易被他人理解、保证代码可靠性
总共有 23 种设计模式。这些模式可以分为三大类:创建型模式(Creational Patterns)、结构型模式(Structural Patterns)、行为型模式(Behavioral Patterns) 。 额外的另一类设计模式:J2EE 设计模式。
1、开闭原则(Open Close Principle)
开闭原则的意思是:对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。简言之,是为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。
避免修改过去的代码,通过扩展的方式升级系统: 接口和抽象类
2、里氏代换原则(Liskov Substitution Principle)
里氏代换原则是面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。LSP 是继承复用的基石,只有当派生类可以替换掉基类,且软件单位的功能不受到影响时,基类才能真正被复用,而派生类也能够在基类的基础上增加新的行为。里氏代换原则是对开闭原则的补充。实现开闭原则的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。
任何基类可以出现的地方,子类一定可以出现。
3、依赖倒转原则(Dependence Inversion Principle)
这个原则是开闭原则的基础,具体内容:针对接口编程,依赖于抽象而不依赖于具体。
4、接口隔离原则(Interface Segregation Principle)
这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。它还有另外一个意思是:降低类之间的耦合度。由此可见,其实设计模式就是从大型软件架构出发、便于升级和维护的软件设计思想,它强调降低依赖,降低耦合。
使用多个接口,每个接口负责一定的功能。
5、迪米特法则,又称最少知道原则(Demeter Principle)
最少知道原则是指:一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。
使用行为型模式:其关注对象之间的通信
6、合成复用原则(Composite Reuse Principle)
合成复用原则是指:尽量使用合成/聚合的方式,而不是使用继承。
Spring的ApplicationContext在添加对Message,Environment,Event和Source的支持的时候,没有在BeanFactory上扩展,而是选择把BeanFactory作为一个内部属性调用,对其他配置的支持,通过继承不同的接口实现(ConfigurableApplicationContext)。
序号 | 模式 & 描述 | 包括 |
---|---|---|
1 | 创建型模式 这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 new 运算符直接实例化对象。这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。 |
工厂模式(Factory Pattern) 抽象工厂模式(Abstract Factory Pattern) 单例模式(Singleton Pattern) 建造者模式(Builder Pattern) 原型模式(Prototype Pattern) |
2 | 结构型模式 这些设计模式关注类和对象的组合。继承的概念被用来组合接口和定义组合对象获得新功能的方式。 |
适配器模式(Adapter Pattern) 桥接模式(Bridge Pattern) 过滤器模式(Filter、Criteria Pattern) 组合模式(Composite Pattern) 装饰器模式(Decorator Pattern) 外观模式(Facade Pattern) 享元模式(Flyweight Pattern) 代理模式(Proxy Pattern) |
3 | 行为型模式 这些设计模式特别关注对象之间的通信。 |
责任链模式(Chain of Responsibility Pattern) 命令模式(Command Pattern) 解释器模式(Interpreter Pattern) 迭代器模式(Iterator Pattern) 中介者模式(Mediator Pattern) 备忘录模式(Memento Pattern) 观察者模式(Observer Pattern) 状态模式(State Pattern) 空对象模式(Null Object Pattern) 策略模式(Strategy Pattern) 模板模式(Template Pattern) 访问者模式(Visitor Pattern) |
4 | J2EE 模式 这些设计模式特别关注表示层。这些模式是由 Sun Java Center 鉴定的。 |
—————————————————————————— MVC 模式(MVC Pattern) 业务代表模式(Business Delegate Pattern) 组合实体模式(Composite Entity Pattern) 数据访问对象模式(Data Access Object Pattern) 前端控制器模式(Front Controller Pattern) 拦截过滤器模式(Intercepting Filter Pattern) 服务定位器模式(Service Locator Pattern) 传输对象模式(Transfer Object Pattern) |
创建固定类对象的工厂,工厂创建的类可以为一系列有关的对象,如:一个PCFactory可以有getMouse()和getKeyBoard()等创建一系列相关对象的方法。
抽象工厂可以形容为工厂的工厂, 提供一个创建一系列相关或相互依赖对象的接口,而无需指定具体创建的类 。
如: 系统的产品有多于一个的产品族,而系统只消费其中某一族的产品。 PCFactory作为抽象工厂接口,DellPCFactory和HuaWeiPCFactory就可以作为 抽象工厂 的工厂,每个不同的工厂生产不同品牌的产品。
如数据库操作:每一个不同数据库(如MySQL和SQLServer)工厂协议可以生成继承同一个接口但是细节不同的连接。而他们都会继承一个接口,其作为抽象工厂。
持久化一个实例并保存,每一次请求的对象都是同一个。
与通过对一个类进行实例化来构造新对象不同的是,原型模式是通过拷贝一个现有对象生成新对象的。浅拷贝实现 Cloneable,重写,深拷贝是通过实现 Serializable 读取二进制流。
使用场景: 1、资源优化场景。
2、类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。
3、性能和安全要求的场景。
4、通过 new 产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。
5、一个对象多个修改者的场景,且对象中没有共享资源。
6、一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。
7、在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过 clone 的方法创建一个对象,然后由工厂方法提供给调用者。原型模式已经与 Java 融为浑然一体,大家可以随手拿来使用。
8、 当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。
一个 Builder 类会一步一步构造最终的对象。该 Builder 类是独立于其他对象的。
场景:
Spring UriComponentsBuilder与 UriComponents
当然,所有的一切都可以通过类本身进行构建,不需要Builder,但是由于太复杂,比如夸张一点,有100个属性,其中非常多的非必填,这些属性的组合非常多,用构造函数表示则非常多,如果全部在初始化赋值,会有非常多的属性赋值为null,这个时候就可以用建造者模式。其对类内属性的操作用:
public Builder param(int param ) {
this.param = param;
return this;
}
// 一般构建都会写成:
Object object= Builder.instance().para().otherpara().operation().otherOperation().build()
适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁。
适配器继承或依赖已有的对象,实现想要的目标接口。
适配器不是在详细设计时添加的,而是解决正在服役的项目的问题。
https://www.cnblogs.com/tongkey/p/7919401.html
spring案例: spring事件与监听器 ApplicationListenerMethodAdapter
首先简要介绍一下spring的事件监听:
spring事件监听是在监听的方法上使用@ListenerEvent进行注册,最终把这个方法注册为一个监听器。
ApplicationListenerMethodAdapter
实现了GenericApplicationListener
接口,他有两个作用,一个是保存方法的一些信息,第二是通过反射的方法实现对这些方法的调用(对方法的调用写在对GenericApplicationListener实现方法中,本来GenericApplicationListener中实现的东西由于需要使用到反射所以GenericApplicationListener是一个接口,实现全部在ApplicationListenerMethodAdapter中)。这就是一个方法调用到监听器的一个转变或者说适配。
最终这些监听器被保存在AbstractApplicationContext的Set<> applicationListeners
中。
桥接模式即将抽象部分与它的实现部分分离开来,使他们都可以独立变化。
抽象化:其概念是将复杂物体的一个或几个特性抽出去而只注意其他特性的行动或过程。在面向对象就是将对象共同的性质抽取出去而形成类的过程。
实现化:针对抽象化给出的具体实现。它和抽象化是一个互逆的过程,实现化是对抽象化事物的进一步具体化。
脱耦:脱耦就是将抽象化和实现化之间的耦合解脱开,或者说是将它们之间的强关联改换成弱关联,将两个角色之间的继承关系改为关联关系。
给一个案例:编写一个可以画不同颜色不同形状的程序。
颜色和形状属于不同的维度——抽象出来;
不同的颜色实现、不同的形状实现——实现化;
脱耦合
具体实现就是由一端保持另一端的引用,在使用到这部分的时候调用就行。一端到另一端就是桥接。
public class Shape{
DrawColor drawColor;
public Shape(DrawColor drawColor){
this.drawColor = drawColor;
}
public void draw(){
drowShape();
drawColor.drawColor();
}
}
接力型,这种模式允许开发人员使用不同的标准来过滤一组对象,通过逻辑运算以解耦的方式把它们连接起来。这种类型的设计模式属于结构型模式,它结合多个标准来获得单一标准。
用于把一组相似的对象当作一个单一的对象 。
将对象组合成树形结构以表示”部分-整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
组合模式使得用户对单个对象和组合对象的使用具有一致性。 这句话就是组合模式的应用过程。Listener,监听器就是典型的场景。对一个事件注册了多个监听器,可以使用组合模式,将多个监听器保存在组合模式的监听器中,事件触发则循环调用所有监听器的方法。
任何对于某个点的共同不相互影响的操作都可以使用组合模式,其实就是遍历操作。
如:org.springframework.statemachine.action.CompositeActionListener
装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。不使用继承的方式。
在不想增加很多子类的情况下扩展类。
具体扩展类重写父类方法 。
举个栗子:食品加工厂的工人都实现同一个接口worker有着同一个方法process。实现 worker 表示不同类型的工人。这时有一个需求是对部分类型的工人在其加工之前或之后加入一些动作。就可以使用装饰者模式。
public interface Worker{ void process();}
public ATypeWorker implements Worker{ public void process(){ ...doA();}}
public BTypeWorker implements Worker{ public void process(){ ...doB();}}
public CTypeWorker implements Worker{ public void process(){ ...doC();}}
public WorkerDecorator implements Worker{
Worker worker;
public WorkerDecorator(Worker worker){
this.worker = worker;
}
public void woker(){
doSomethingNeedBefore();
worker.process();
doSomethingNeedAfter();
}
}
看起来像是可以用AOP切面添加执行方法,但是装饰者模式是为了在不通过继承来扩展类的方法,且可以横向扩展(一次扩展多个子类)。
意图:为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
主要解决:降低访问复杂系统的内部子系统时的复杂度,简化客户端与之的接口。
何时使用: 1、客户端不需要知道系统内部的复杂联系,整个系统只需提供一个”接待员”即可。 2、定义系统的入口。
举个栗子:Dubbo
享元模式(Flyweight Pattern)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结构的方式。
减少对象的创建,降低系统的内存,使效率提高。
应用实例: 1、JAVA 中的 String,如果有则返回,如果没有则创建一个字符串保存在字符串缓存池里面。 2、数据库的数据池。
大概意思就是保存一些对象,防止对同一类对象反复的创建销毁。
具体实现:存起来,如HashMap存一下,通过String标识符获取。
代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。通俗的来讲代理模式就是我们生活中常见的中介。
注意事项: 1、和适配器模式的区别:适配器模式主要改变所考虑对象的接口,而代理模式不能改变所代理类的接口。 2、和装饰器模式的区别:装饰器模式为了增强功能,而代理模式是为了加以控制。
静态代理模式和装饰器模式没有什么区别,在代理类不多的情况下,使用静态代理,实例和装饰器模式是一样的。只不过是目的不一样,装饰器模式是为了在其本身的行为模式下进行拓展,代理是为加以控制(添加日志,处理参数……)
主要还是动态代理,通过反射实现。
案例讲解:https://www.cnblogs.com/carpenterlee/p/8241042.html
https://www.cnblogs.com/carpenterlee/p/8241042.html
意图:避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。
主要解决:职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递,所以职责链将请求的发送者和请求的处理者解耦。
所有的处理器都在一个链表上,每处理一个消息都要从链表头到表尾。
请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令。
将一个请求封装成一个对象,从而使可以用不同的请求对客户进行参数化。
优点: 1、降低了系统耦合度。 2、新的命令可以很容易添加到系统中去。
案例讲解:https://www.cnblogs.com/jmcui/p/10042235.html
1、命令(Command):为所有命令声明了一个接口。调用命令对象的 execute()方法,就可以让接收者进行相关的操作。这个接口也具备一个 undo() 方法。
https://www.cnblogs.com/jmcui/p/10042235.html
2、具体命令(ConcreteCommand):实现命令接口,定义了动作和接收者之间的绑定关系。调用者只要调用 execute() 就可以发出请求,然后由 ConcreteCommand 调用接收者的一个或多个动作。
3、请求者(Invoker):持有一个命令对象,有一个行动方法,在某个时间点调用命令对象的 execute() 方法,将请求付诸实行。
4、接收者(Receiver):接收者知道如何进行必要的动作,实现这个请求。任何类都可以当接收者。最后执行动作的那个对象。
5、客户端(Client):创建一个具体命令(ConcreteCommand)对象并确定其接收者,包括把其他角色串连在一起。
一句话总结:Invoker保存了所有的command(ConcreteCommand),客户端使用参数调用具体的ConcreteCommand,command调用与Receiver关联的方法执行一个或多个动作。
类比:Spring的Controller
DispacherServlet相当于Invoker,然后所有的Controller下的RequestMapping都会被注册为MappingRegistration,作为一个Handler(这就相当于一个命令),这些MappingRegistration中保存着路径与方法,最终通过路径寻找到Handler调用这些方法。这些方法中再调用一个或多个Service,这些Service也就相当于Receiver。
意图:给定一个语言,定义它的文法表示,并定义一个解释器,这个解释器使用该标识来解释语言中的句子。
如何解决:构建语法树,定义终结符与非终结符。
1、非终结符可以bai再分成更细的东西。
2、不是终结符的都是非终结符。非终结符可理解为一个可拆分元素,而终结符是不可拆分的最小元素。终结符号就是语言中用到的基本元素,名词、动词、形容词、助词等等基本语言单位。
应用实例:编译器、运算表达式计算、 SQL 解析、符号处理引擎、Spring Expression Language(SpEL)。
SpEL是一种由Spring的org.springframework.expression.ExpressionParser实现分析和执行的语言。使用作为字符串给出的Spel表达式,并将Spel表达式转换为org.springframework.expression.Expression的实例。
意图:提供一种方法顺序访问一个聚合对象中各个元素, 而又无须暴露该对象的内部表示。
用以遍历一个聚合对象。
关键代码:定义接口:hasNext, next。
应用实例:JAVA 中的 iterator
。Python的__iter__() ,iter()
中介者模式(Mediator Pattern)是用来降低多个对象和类之间的通信复杂性。这种模式提供了一个中介类,该类通常处理不同类之间的通信,并支持松耦合,使代码易于维护。中介者模式属于行为型模式。
意图:用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
主要解决:对象与对象之间存在大量的关联关系,这样势必会导致系统的结构变得很复杂,同时若一个对象发生改变,我们也需要跟踪与之相关联的对象,同时做出相应的处理。
何时使用:多个类相互耦合,形成了网状结构。
如何解决:将上述网状结构分离为星型结构。
关键代码:对象 Colleague 之间的通信封装到一个类中单独处理。
应用实例: 1、中国加入 WTO 之前是各个国家相互贸易,结构复杂,现在是各个国家通过 WTO 来互相贸易。 2、机场调度系统。 3、MVC 框架,其中C(控制器)就是 M(模型)和 V(视图)的中介者。
优点: 1、降低了类的复杂度,将一对多转化成了一对一。 2、各个类之间的解耦。 3、符合迪米特原则
备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对象。备忘录模式属于行为型模式。
1、需要保存/恢复数据的相关状态场景。 2、提供一个可回滚的操作。
案例:1、后悔药。 2、打游戏时的存档。 3、Windows 里的 ctri + z。 4、IE 中的后退。 4、数据库的事务管理。
当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知依赖它的对象。观察者模式属于行为型模式。
意图:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
组合模式中我讲过一个例子,这个也可以看做是一个一对多的场景:
Listener,监听器就是典型的场景。对一个事件注册了多个监听器,可以使用组合模式,将多个监听器保存在组合模式的监听器中,事件触发则循环调用所有监听器的方法。
任何对于某个点的共同不相互影响的操作都可以使用组合模式,其实就是遍历操作。
如:org.springframework.statemachine.action.CompositeActionListener
因为CompositeActionListener是观察者模式和组合模式的组合。还有一个例子,Spring的事件监听,这就是一个观察者模式,一个Event被Publish,则所有监听这个Event事件的监听器都将调用并执行,spring的事件监听使用了适配器模式。
在状态模式(State Pattern)中,类的行为基于它的状态改变。
意图:允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类。
主要解决:对象的行为依赖于它的状态(属性),并且可以根据它的状态改变而改变它的相关行为。
何时使用:代码中包含大量与对象状态有关的条件语句。
如何解决:将各种具体的状态类抽象出来。
代替if else
空对象:不包含信息,不做任何操作的对象。
该空对象类用于需要检查空值的地方。替代Null。
意图:定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。
菜鸟教程或者其他一些网上的博客,把策略模式用作解决if…else所带来的复杂和难以维护 ? ???? ?
怎么可能是这种回事。解决if…else 不是重点。
策略模式的实现是为了动态地让一个对象在许多行为中选择一种行为;动态地在几种算法中选择一种;
实现某一方面的特性,通过实现统一的接口就可以了,然后类中有这个接口作为他的实现类的引用类型。不同实现类相互之间替换。
策略模式注意点:
https://blog.csdn.net/qq_39969226/article/details/88069038
1.分析项目中变化部分与不变部分
2.多用组合少用继承;用行为类组合,而不是行为的继承。更有弹性。
3.设计模式没有相应的库直接使用,有些库或框架本身就用某种设计模式设计的。
和适配器模式、装饰器模式、代理模式的区别
虽然目的不同但是适配器模式、装饰器模式、代理模式这三者本身都是需要实现接口(需要适配的接口,需要装饰扩展的接口、需要代理控制的接口)的。
但是策略模式不同,他需要这个功能,但是只是需要用,并不属于这个特性的类。如人类可以手拿工具完成一些事情,但是并不需要实现工具的接口(或继承类,人不等于工具),只需要保持一个工具具体实现类的对象的一个引用,通过工具所做的事情交给工具的接口。
public interface Tool{
void useTool();
}
public class People{
private Tool tool;
public People(Tool tool){
this.tool = tool;
}
public void useToolDoSomething(){
tool.useTool();
}
}
感觉没有必要作为一个模式
在模板模式(Template Pattern)中,一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。这种类型的设计模式属于行为型模式。
意图:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
主要解决:一些方法通用,却在每一个子类都重新写了这一方法。
Spring中大量使用了模板模式,比如ApplicationContext中获取BeanFactory的方法(如:getAutowireCapableBeanFactory
),最终是通过getBeanFactory
这个方法获得的,代码如下。
@Override
public abstract ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException;
这个方法就是抽象的,其具体实现由ApplicationContext的子类(AbstractRefreshableApplicationContext、GenericApplicationContext)实现,这个方法是通用的,但是怎么实现由子类自己决定,父类不管,所以就抽象了出来,使得子类必须要实现它。
或者说抽象这个概念本身就是为了把公共的代码抽取出来公共实现,其他方法由子类按照自己的想法实现。
意图:主要将数据结构与数据操作分离。
https://www.jianshu.com/p/1f1049d0a0f4
MVC 模式代表 Model-View-Controller(模型-视图-控制器) 模式。这种模式用于应用程序的分层开发。