设计模式是前人(一般都是大师)对程序设计经验的总结,学习并应用设计模式可以使我们开发的项目更加规范、便于扩展和维护,这大概是为什么设计模式基本是面试必问的原因吧!
本文主要内容有四部分:设计原则、设计模式分类、23种设计模式、设计模式应用。设计模式虽多,但重要的、主流开源框架应用较多的往往就那几个。
本文主要内容是参考了其他网站内容结合自己的理解总结而成,希望能帮到大家,如果觉得有收获记得关注哦!
1. 设计原则
参考链接:
http://www.uml.org.cn/sjms/201211023.asp
http://www.uml.org.cn/j2ee/201301074.asp
1.1. 单一职责原则
定义:即一个类只负责一项职责。
问题由来:如果类T有两个职责:P1, P2。则当P1发生需求变动需要修改修改代码时,可能会影响到P2的功能。
解决方案:遵循单一职责原则,分别建立两个类T1, T2 来完成P1, P2的功能。使P1, P2的变更互不影响。
优点:可以降低类的复杂度;解耦;提高类的可读性、提高系统的可维护性。
1.2. 里氏替换原则
概述: 子类可以扩展父类的功能,但不能改变父类原有的功能。
它包含以下含义:
1) 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
2) 子类的方法重载父类的方法时,方法的入参要比父类方法的入参更宽松。
3) 子类的方法实现父类的抽象方法时,方法的返回值要比父类更严格。
问题由来:
有一功能P1,由类A完成。现需要将功能P1进行扩展,扩展后的功能为P,其中P由原有功能P1与新功能P2组成。新功能P由类A的子类B来完成,则子类B在完成新功能P2的同时,有可能会导致原有功能P1发生故障。
解决方案:当使用继承时,遵循里氏替换原则。类B继承类A时,除添加新的方法完成新增功能P2外,尽量不要重写父类A的方法,也尽量不要重载父类A的方法。
优点:提高系统的可维护性。
1.3. 依赖倒置原则
定义:上层模块不应该依赖低层模块的实现,而应该依赖其抽象。
问题由来:类A直接依赖类B,假如要将类A改为依赖类C,则必须通过修改类A的代码来达成。修改类A,会给程序带来不必要的风险。
解决方案:将类A修改为依赖接口I,上层模块无需关心底层模块的接口实现细节。依赖倒置原则的核心就是要我们面向接口编程。
依赖倒置原则需要注意:
1) 低层模块提高的API使用抽象类或接口
2) 变量的声明类型尽量是抽象类或接口
1.4. 接口隔离原则
定义:一个类对另一个类的依赖应该建立在最小的接口上。
问题由来:类A通过接口I依赖类B,类C通过接口I依赖类D,如果接口I对于类A和类B来说不是最小接口,则类B和类D必须去实现他们不需要的方法。
解决方案:将臃肿的接口I拆分为独立的几个接口,类A和类C分别与他们需要的接口建立依赖关系。也就是采用接口隔离原则。
接口隔离原则要注意:
1) 设计较小的接口。
2) 提高内聚,减少对外交互。
1.5. 迪米特法则
定义:一个对象应该对其他对象保持最少的了解。
问题由来:类与类之间的关系越密切,耦合度越大,当一个类发生改变时,对另一个类的影响也越大。
解决方案:尽量降低类与类之间的耦合。
迪米特法则又叫最少知道原则,提倡我们编写低耦合,高内聚的程序。模块之间的耦合越低,代码复用率越高。
例如在方法入参中声明集合类型的参数时应该声明为集合的接口而不是具体实现类,因为通常我们无需关心具体是哪一种集合实现类。这样该方法可以接受更多的入参类型,可以提高该方法的利用率。
1.6. 开闭原则
定义:一个类或方法应该对扩展开放,对修改关闭。
问题由来:随着时间的变化需要对系统进行升级、优化、扩展等。这时可能需要修改原有代码,可能会影响到原有的功能。
解决方案:尽量通过扩展现有代码来实现变化,而不是修改现有代码。
优点:可提高系统的可维护性,可以灵活扩展。
2. 设计模式分类
一般地设计模式分为三类共23种:
2.1. 创建型模式
单例模式、抽象工厂模式、建造者模式、工厂模式、原型模式。
2.2. 结构型模式
适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式。
2.3. 行为型模式
模版模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、责任链模式、访问者模式。
3. 23种设计模式
参考链接:
http://www.uml.org.cn/sjms/sjms-bk.asp
http://www.runoob.com/design-pattern/design-pattern-tutorial.html
https://www.journaldev.com/1827/java-design-patterns-example-tutorial
3.1. 单例模式
3.1.1. 概要
单例模式确保一个类仅有一个实例,并提供一个全局访问点。
单例模式双重锁检查模式实现需要通过volatile和synchronized来实现。
3.1.2. 使用场景
避免重复创建实例带来的时间和空间的开销。
3.2. 工厂模式
3.2.1. 概要
工厂模式(Factory Pattern)定义一个用于创建对象的接口,让子类决定将哪一个类实例化。Factory Method使一个类的实例化延迟到其子类。
3.2.2. 使用场景
不同条件下创建不同类的实例时。
3.3. 抽象工厂模式
3.3.1. 概要
抽象工厂模式(Abstract Factory Pattern)是围绕一个超级工厂创建其他工厂。提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
3.3.2. 使用场景
系统有多个产品族时。
3.4. 建造者模式
3.4.1. 概要
建造者模式(Builder Pattern)使用多个简单的对象一步一步构建成一个复杂的对象。
3.4.2. 使用场景
当类有很多需要灵活设置的参数时可以使用建造者模式。《Effective Java》曾提到相比于重叠构造器模式,Builder模式更便于使用。如StringBuilder以及mongodb中使用
MongoClientOptions.Builder来构建其参数配置。
3.5. 原型模式
3.5.1. 概要
原型模式(Prototype Pattern)主要是通过克隆等方式减小重复创建复杂对象的开销。
3.5.2. 使用场景
减小重复创建复杂对象的开销。
3.6. 适配器模式
3.6.1. 概要
适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁。适配器将一个非标准接口转换为标准接口。
3.6.2. 使用场景
主要解决新老版本兼容问题或不同的产品之间的兼容问题,应用场景有jdbc, IO中的InputStreamReader, OutputStreamWriter。
3.6.3. 实现
实现标准接口,持有依赖对象。如InputStreamReader实现了Reader,持有了InputStream。
3.7. 桥接模式
3.7.1. 概要
桥接模式(Bridge Pattern)把抽象化与实现化解耦,使得二者可以独立变化。
3.7.2. 角色
桥接接口,桥接接口实现类,抽象类,抽象类实现类
3.7.3. 使用场景
实现系统可能有多个角度分类,每一种角度都可能变化。把这种多角度分类分离出来,让它们独立变化,减少它们之间耦合。
3.7.4. 实现
抽象类的抽象方法依赖桥接接口。
3.8. 装饰模式
3.8.1. 概要
装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种模式创建了一个装饰类,用来包装原有的类,并在保持原有类方法签名不变的前提下,提供了额外的功能。
3.8.2. 使用场景
需要为类添加一些额外的功能,但又不影响原有类的功能。如BufferedInputStream.
3.8.3. 实现
实现原有类的接口,并注入原有实现类的引用,在实现原有接口的方法时调用原有的实现方法并增加新的代码。
3.9. 组合模式
3.9.1. 概要
组合模式(Composite Pattern)将对象组合成树形结构以表示"部分-整体"的层次结构。
3.9.2. 使用场景
需要封装整体-部分的层级结构时,如设计树形菜单、产品系列、文件夹。
3.9.3. 实现
用同一个类来封装父对象和子对象,并封装其父子关系。
3.10. 外观模式
3.10.1. 概要
外观模式(Facade Pattern)隐藏系统的复杂性,并向客户端提供了一个便于访问相关服务的接口。
3.10.2. 使用场景
当为客户端提供的接口较多、较复杂时可以通过外观模式简化客户端访问。
3.11. 享元模式
3.11.1. 概要
享元模式(Flyweight Pattern)主要用于减少创建对象的数量,以减少内存占用和提高性能。
3.11.2. 使用场景
需要减少重复创建对象的开销时。如java包装类中的Byte,Short,Integer,Long都会缓存-128到127的包装类实例。
3.12. 代理模式
3.12.1. 概要
代理模式(Proxy Pattern)就是给某一个对象创建一个代理对象,通过代理对象控制对原对象的引用,而创建代理对象时可以增加一些额外的操作。
3.12.2. 使用场景
在类访问需要做一些控制时,如安全控制、日志记录等。应用如Spring AOP。
3.12.3. 实现
实现类与代理类组合。
3.13. 模版模式
3.13.1. 概要
模板模式(Template Pattern)在抽象类中定义了主方法的模板或执行步骤。
3.13.2. 使用场景
一些通用的算法可以抽象出来时。
3.14. 命令模式
3.14.1. 概要
命令模式(Command Pattern)以命令的形式封装请求对象,请求被调用者(invoker)封装后发送给接收者(receiver),由接收者来执行命令。命令模式主要作用就是封装命令,把发出命令的责任和执行命令的责任分开。
3.14.2. 使用场景
认为是命令的地方都可以使用命令模式.如空调遥控器。
3.15. 迭代器模式
3.15.1. 概要
迭代器模式(Iterator Pattern)用于遍历集合中的元素,而不需要知道集合对象的底层表示。
3.15.2. 使用场景
如java中的Iterator.
3.16. 观察者模式
3.16.1. 概要
当一个对象被修改后需要通知它的依赖对象,并且该对象与依赖对象是一对多的关系时可以使用观察者模式(Observer Pattern)。
3.16.2. 使用场景
观察者模式使用三个类 Subject、Observer 和 Client。
3.16.3. 实现
观察者模式主要有三个角色 主题(Subject)、观察者(Observer) 和 客户端(Client)。一个主题有多个观察者,当主题发生变化时需要通知该主题下所有的观察者。
3.17. 中介者模式
3.17.1. 概要
中介者模式(Mediator Pattern)提供了一个中介类,该类通常处理不同类之间的通信,并支持松耦合,使代码易于维护。
3.17.2. 使用场景
多个类相互耦合,形成了网状结构。中介者模式可将网状结构分离为星型结构。
3.18. 备忘录模式
3.18.1. 概要
备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对象。
3.18.2. 使用场景
需要保存/恢复数据的相关状态场景。提供一个可回滚的操作。
3.19. 解释器模式
3.19.1. 概要
解释器模式(Interpreter Pattern)实现了一个表达式接口,该接口解释一个特定的上下文。
3.19.2. 使用场景
SQL 解析、符号处理引擎等。
3.20. 状态模式
3.20.1. 概要
在状态模式(State Pattern)中,类的行为是基于它的状态改变的。
3.20.2. 使用场景
代码中包含大量与对象状态有关的条件语句。
3.21. 策略模式
3.21.1. 概要
策略模式(Strategy Pattern)中,一个类的行为或其算法可以在运行时更改。定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。
3.21.2. 使用场景
系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。一个系统需要动态地在几种算法中选择一种。
3.22. 责任链模式
3.22.1. 概要
责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。
3.22.2. 使用场景
职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递,所以职责链将请求的发送者和请求的处理者解耦了。如日志框架的实现。应用有servlet中的filter。
3.23. 访问者模式
3.23.1. 概要
访问者模式(Visitor Pattern)使用了一个访问者类,元素的执行算法可以随着访问者改变而改变。
3.23.2. 使用场景
访问者模式将数据结构与数据操作分离。
4. 经典设计模式应用
4.1. JDK之设计模式
参考链接:https://blog.csdn.net/gtuu0123/article/details/6114197
适配器模式: InputStreamReader, OutputStreamWriter.
装饰器模式: BufferedInputStream, Collections#synchronizedList(List)
享元模式:IntegerCache, String常量池.
代理模式:Proxy
策略模式:RejectedExecutionHandler
命名模式:ThreadPoolExecutor中的Runnable
4.2. Spring之设计模式
参考链接:
https://www.ibm.com/developerworks/cn/java/j-lo-spring-principle/index.html
4.2.1. 代理模式
Spring Aop中的两种动态代理Jdk、CGlib使用了代理模式。
4.2.2. 策略模式
Spring中代理对象的创建使用了策略模式。抽象策略是AopProxy接口,Cglib2AopProxy和JdkDynamicAopProxy分别代表两种策略的实现方式,ProxyFactoryBean就是代表Context角色,它根据条件选择使用Jdk代理方式还是CGLIB方式。
4.2.3. 其他
Spring中应用到的其他设计模式有:
单例模式;
适配器模式:
spring AOP中的MethodBeforeAdviceAdapter类把Advice适配为MethodInterceptor对象;
装饰器模式: BeanDefinitionDecorator, BeanWrapper, HttpRequestWrapper;
观察者模式: ApplicationListener, ContextLoaderListener等。
模板模式:JdbcTemplate实现了一系列常用的数据访问的算法骨架等。
4.3. tomcat之设计模式
参考链接:
https://www.ibm.com/developerworks/cn/java/j-lo-tomcat2/index.html
4.3.1. 门面模式
tomcat中的RequestFacade应用门面模式(facade)来封装HttpServletRequest。
4.3.2. 观察者模式
观察者模式也叫发布-订阅模式,也就是事件监听机制,通常在某个事件发生的前后会触发一些操作。tomcat中控制组件生命周期的 Lifecycle 、Servlet 实例的创建、Session 的管理、Container 管理等应用了观察者模式。相关的类有LifecycleListener, ContainerListener, SessionListener…
4.3.3. 命令模式
Tomcat 中命令模式在 Connector 和 Container 组件之间有体现,Tomcat 作为一个应用服务器,无疑会接受到很多请求。
Connector 作为抽象请求者,HttpConnector 作为具体请求者。HttpProcessor 作为命令。Container 作为命令的抽象接受者,ContainerBase 作为具体的接受者。客户端就是应用服务器 Server 组件了。
4.3.4. 责任链模式
tomcat的容器设置就是责任链模式,从Engine到Host再到Context一直到Wrapper都是通过一个链传递请求。