导语:设计模式是无数码农前人在实际的生产项目中经过不断的踩坑、爬坑、修坑的经历总结出来的经验教训,经过抽象之后表达成的概念。能够帮助后来的设计者避免重复同样的错误或者弯路。
我抽空整理了一下设计模式,用自己的话总结了一下,自认为通俗易懂。
简单工厂模式:
包括三种角色,抽象产品、具体产品和工厂角色。其中在工厂直接完成对具体产品的创建。工厂模式的好处是需要创建对象的时候只需要输入一个正确的参数就可以获得所需要的对象,而无需知道其创建细节,这种模式将对象的创建和对象业务的处理分离,降低系统的耦合度,使得两者修改起来都相对容易。
工厂方法模式:
该模式包括四种角色,抽象工厂、具体工厂、抽象产品和具体产品角色。与简单工厂模式相比,抽象工厂负责定义创建产品对象的公共接口,而工厂子类则负责生产具体的产品对象。这样可以将产品类的实例化操作延迟到工厂子类中完成。这样,当需要生成一个具体产品对象时,首先要生成该对象的产品工厂。这样的好处是可扩展性很好。
抽象工厂模式:
在工厂方法模式中,具体工厂负责生产具体的产品,每一个具体工厂对应一种具体产品,工厂方法也具有唯一性,一般情况下一个具体工厂中只有一工厂方法或者一组重载的工厂方法,但是有的时候我们需要一个工厂可以提供多个产品对象,而不是单一的产品对象。抽象工厂模式是为了处理对象具有等级结构以及对象族的问题。对象的等级结构即是对象的继承结构,比如电视机是一个抽象父类,其子类有海尔电视机、海信电视机、TCL电视机等。对象族:是指同一个工厂生产的、位于不同对象等级结构中的一组对象。如海尔生产的海尔电视机、海尔电冰箱,这两个都位于海尔电器的产品族中,其中海尔电视机位于电视机等级结构中,海尔电冰箱位于电冰箱等级结构中。
抽象工厂模式包括抽象工厂、具体工厂、抽象产品、具体产品4中角色。每一个具体工厂代表了一个对象族,都提供了多个工厂方法用于生产多种不同类型的产品,这些产品构成了一个对象族。
使用抽象工厂模式创建对象的时候,首先创建对应该对象族的工厂,然后调用该工厂中创建该对象的方法。
建造者模式:
建造者模式讲一个复杂对象的构建与它的表示分离,使得童颜的构建过程可以创建不同的表示,它允许用户只通过指定复杂对象的类型和内容就可以创建它们,而不需要参与内部的具体构建细节。建造者模式包括4种角色:指挥者角色、抽象构造者角色、具体构造者角色以及产品角色。具体构造者负责生成具体的对象、指挥者一方面隔离了客户和生产过程,另一方面负责监控产品的生成过程,比如复杂对象的各个部分是以怎样的顺序生成等。指挥者针对抽象建造者编程,客户端只需要知道具体构造者的类型,即可通过指挥者类调用构造者的相关方法,返回一个完整的产品对象。
距离说明,去KFC点餐的过程。一份套餐需要三个对象组成,即:可乐、汉堡和薯条。生产这些对象的师傅即为具体建造者,服务员为指挥者,套餐为产品角色。显然,师傅在后台如何生产这些产品我们并不知道,而是有服务员把这些对象构造成一份套餐。
原型模式:
原型设计模式:如果一些对象的创建构成比较复杂,而且有时候需要频繁的创建,原型模式通过给出一个原型对象来指明索要创建的对象的类型,然后用复制这个原型的办法创建出跟多同类型的对象。浅拷贝、深拷贝。
原型模式的适用场景:
Struts2中为了保证线程安全性,Action对象的创建使用了原型模式,访问一个已经存在的Action时将通过克隆的方式创建出一个新的对象。从而保证其中定义的变量无须进行加锁实现同步,每一个Action中都有自己的成员变量,避免Struts1因使用单例模式而导致的并发和同步问题。
Spring中,用户也可以采用原型来创建新的bean实例,从而实现每次获取的是通过克隆生成的新实例,对其进行修改时对原有实例对象不造成任何影响。
单例设计模式:
单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类成为单例类。单例模式的实现一般是构造函数私有化、自行实例化一个实例并提供静态函数供整个系统访问这个实例。其应用场景包括:java.lang.Runtimr类、线程池ThreadPool类中。
适配器设计模式:
适配器模式是将一个类的接口转换成客户希望的另一个接口,使得原本由于接口不兼容而不能一起工作的类可以一起工作。包括四个角色:客户端、目标接口、适配器、适配者。
适用场景:已经存在的类的接口具有实用的功能但是不符合我们的要求。比如我们在程序中某个类C最终面对的是A接口或抽象类,因此类C只能使用A的子类或者实现类。假设我们有B类含有特殊的操作或功能,现在我们想在自己的系统中使用它,我们可以将其转化成符合我们的标准的类。这样,C类就可以在透明的情况下任意的选择使用C类的子类或者具有特殊功能的B类。根据转化的方式可以分为类适配器和对象适配器。
类适配器:自定义适配器,该适配器继承自B,并且实现了A,这样根据多态性,类C就可以很对该适配器编程。
对象适配器:适配器采用组合的形式,不再继承B,而是关联一个B对象。
适配器模式的应用场景:Hibernate自带的日志系统是sel4j,自带了一个jar包,slf4j-api-1.5.8.jar。打开这个jar包我们发现只是定义了一些接口,没有具体实现。如果想用slf4j日志系统,需要引入slf4j-nop-1.5.8.jar实现jar包。但是这个日志系统没有log4j使用的广泛,因此Hibernate提供了一个jar包:slf4j-log4j12-1.5.8.jar,然后我们再引入log4j.jar,这样通过slf4j-log4j12-1.5.8.jar的适配作用,我们就可以通过调用slf4j的接口使用log4j的日志功能。这是Hibernate中典型的适配器模式设计。
桥接模式:
桥接模式的设计思想是让抽象部分与他的实现部分分离,使他们可以独立的变化。它主要应对的是:由于实际的需要,某个类具有两个或两个以上的维度变化,如果只是用继承将无法实现这种需要,或者使得设计变得相当臃肿。
比如说现在我要设计一个通用的日志记录工具。它支持数据库记录databaseLog和文本文件记录FileLog两种方式,同时既可以运行在.net平台和java平台。按照继承的思路,我们首先抽象出一个Log基类,各种不同的日志记录方式都要继承这个类。在这里这两种日志格式都要继承Log类。另外考虑到不同平台的日志记录,对于操作数据库、写入文本文件锁调用的方式可能是不一样的,因此需要提供各种不同平台上的实现,对上面的类因此得到四种继承方式。但是,这种格式使得设计变得相当臃肿。如果我有5中日志格式、5中运行平台,则需要25中继承方式。我们可以看到这种臃肿的继承关系造成的根源在于引起Log变化的原因有两个,即日志记录方式的变化和日志记录平台的变化。现在我们要解耦这两个方向的变化,把他们之前强耦合的继承关系便为弱耦合的关联关系。这就是桥接模式。
我们可以把记录记录格式和日记记录在不同平台的实现分别当作两个独立的部分来对待,对于日志记录方式,类结构仍是databaseLog类和FileLog两个类继承Log类。对于日志记录平台,引入另外一个抽象类,ImpLog,它是日志记录在不同平台上的实现的基类。这个时候,对于日志记录方式和不同的运行平台这两个类可以独立的变化了。我们要做的就是把这两个部分连接起来。在这里使用对象组合的方式,即在Log类中关联一个ImpLog对象。可以看到,通过对象组合的方式,Bridge模式把两个角色之间的继承关系改为了耦合的关系,从而使这两者可以从容自若的各自独立的变化,这也是Bridge模式的本意。
1、JDBC驱动程序也是桥接模式的应用之一。使用JDBC驱动程序的应用系统就是抽象角色,而所使用的数据库是实现角色。一个JDBC驱动程序可以动态地将一个特定类型的数据库与一个Java应用程序绑定在一起,从而实现抽象角色与实现角色的动态耦合。
组合模式:
组合模式描述了如何将容器对象和叶子对象进行递归组合,使得用户在使用时无须对它们进行区分,可以一致的对待容器对象和叶子对象。组合多个对象形成鼠树形结构以表示整体部分的层次结构。组合模式对但对象(即叶子对象)和组合对象(容器对象)的使用具有一致性。组合模式包括抽象组件、叶子组件和容器组件以及客户类。
组合模式的关键是定义了一个抽象类,他既可以代表叶子,也可以代表容器,而客户端针对该抽象组件进行编程,无须知道它到底表示的是叶子还是容器,可以对其进行统一的处理。同时容器对象与抽象构件之间还存在一个聚合关联关系,在容器对象中既可以包含叶子也可以包含容器。比如文件系统的UML图。
实现组合模式有两种思路(1)在抽象组件中定义叶子节点和容器节点公共的方法,比如定义一个表示文件系统节点的抽象组件,可以只定义显示名称的方法,这样在容器组件中扩展抽象组件的方法。(2)在抽象组件中明确定义容器节点所有的方法,这些方法有些肯定是叶子节点不能使用的,比如文件系统中叶子节点不会有remove删除一个文件的方法。这时候抽象组件一般是一个抽象类,抽象类中对这些方法做了一般的处理,而在容器节点中根据功能进行相应的覆盖。Java中XML文档解析以及Java的AWT/SWing均用到了组合模式。
装饰设计模式:
当想要对已有的对象进行功能增强时,可以自定义类,将原有对象传入,基于已有的功能,并提供增前功能,那么自定义的该类称为装饰类(装饰器)。装饰器会通过构造方法接收被装饰的类,并基于被装饰的类提供更强的功能。
装饰设计模式包括抽象构件、具体构件、抽象装饰类和具体装饰类。这里的装饰类就是对具体构件的增强,因此和具体构建一样都是继承与抽象装饰器。
Java中使用的最广泛的装饰器模式就是JavaIO类的设计。比如,OutPutStream是输出流的基类,其子类有FileOutputStream 和FilterOutputStream,而FilterOutputStream的子类有BufferedOutputStream和DataOutputStream两个子类。其中,FileOutputStream为系统的核心类,它实现了向文件写数据的功能,使用DataOutputStream可以在FileOutputStream的基础上增加多种数据类型的写操作支持(DataOutputStream类中有writeUTF、writeInt等函数),而BufferdOutputStream装饰器可以对FileOutputStream增加缓冲功能,优化I/O性能。
外观模式:
一个公司的门户网站就是一个外观模式的应用。考虑网站导航,有公司新闻、留言系统、产品介绍、在线论坛等导航。这样我们先通过导航定位到不同的页面,这样总比直接找到某一页面方便。外观模式定义为:外部与一个子系统的通信必须通过一个统一的外观对象进行,为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得降低了用户和这些子系统的耦合度。
外观设计模式的的设计目标是使系统间的通信和相互依赖关系达到最小,而达到这个目标的途径之一就是引入外观对象,它为 子系统的访问提供了一个简单而单一的入口,同时降低了客户类与子系统类的耦合度。
在java中的应用:jdbc数据库操作,提供一个统一的类来管理对数据库的打开、查询和关闭操作。
享元设计模式:
如果系统中对象数量太多时,由于创建和销毁对象都是很消耗资源的操作,不断的产生新对象将导致运行代价过高,带来性能下降等问题。享元模式正是为解决这类问题而诞生的。享元模式通过共享技术实现对象的重用。在享元模式中通常创建一个享元工厂来维护一个享元池用于存储具有相同内部状态的享元对象。比如说片5000个字母组成的英文文章,我们只需要保存26个字母对象和一些标点符号对象集合,而不是保存5000个对象。因为相同的字母其内部状态(比如其ascii码相同)相同,而外部状态,比如这个字母相对文章开始的偏移量不同,因此这些相同的字母是可以共享的,只是在排列的时候被放到了不同的位置。
享元模式包括四种角色,抽象享元类、具体享元类、非共享具体享元类和享元工厂类。其中享元工厂类维护一个享元抽象类的hashMap,当用户需要具体享元对象的时候,不是直接创建对象,而是从享元工厂的这个hashMap中寻找时候有相同内部状态的对象,如果有则直接返回;如果没有则创建并加入到这个集合后才返回。
Java中应用享元设计模式的场景:JDK中定义String类采用了享元设计模式。Jdk5的并发框架的线程池、以及Integer自动装箱(-128~127的整数)用到了享元设计模式,
代理设计模式:
在某些情况下,一个客户不想直接引用一个对象,此时可以通过一个代理的第三者来实现间接引用,可以在客户端和目标对象之间起到中介的作用,并且可以通过生成代理增加或者删除某些功能。
代理模式包括抽象主题角色、代理主题角色、和真实主题角色。抽象主题是真实主题和代理主题的共同接口,使得在任何使用真实主题的地方都可以使用代理主题(根据里氏代换原则),客户端通常需要针对抽象主题角色编程。
代理模式的应用:
1、JavaRMI的使用。
2、Spring框架中AOP技术也是代理的应用,在Spring AOP中使用动态代理技术。
3、Hibernate根据id进行load查询时,也使用到了代理,用于延迟加载。此时,HIbernate使用一个动态代理子类替代用户定义的类,这样在载入对象时,就不必初始化对象的所有信息。通过代理,拦截原有的getter方法,可以在真正使用对象数据时,才能从数据库加载实际的数据,从而提升。
职责链设计模式:
系统中有很多能处理请求的对象。指责连模式将这些对象连成一条链,并沿着这条链传递该请求,知道有一个对象处理这个请求为止。这样,客户端无须关心请求的处理细节以及请求的传递,只需将请求发送到链上即可,将请求的发送者个处理者解耦。
设计职责链时,→每一个对象及对其上级领导的引用而连接起来形成一条链。请求在这条链上传递,直到链上的某一个对象处理此请求为止,发出这个请求的客户端并不知道链上哪一个对象最终处理这个请求,这使得系统可以在不影响客户端的情况下动态的重新分配和组织责任。
职责链模式在Java中使用最多的就是Java的异常处理机制。
观察者模式:
观察者模式建立一种对象与对象之间的依赖关系,一个对象发生改变时将自动通知其他对象,其他对象将相应做出反应。
观察者模式包括四种角色:目标、具体目标、观察者、抽象观察者。
应用:
1、MVC框架,观察目标为MVC中的model,而观察者就是MVC的View,而Controller充当两者之间的中介者,当模型层的数据发生改变时,试图自动改变其显式内容。
2、Java NIO中的非阻塞通道。SelectableChannel可以向Selector注册读就绪和写就绪等事件。Selector负责监控这些事件,等到事件发生时,比如发生了读就绪事件,SelectableChannel就可以执行读操作了。在这种通信模式中,Selector就充当了观察者。
3、Java语言提供了对观察者模式的支持:在就JDK的java.util包中,提供了Observable目标类和Observer接口(抽象观察者)。
模版设计模式:
需要定义一些顶级逻辑,或者是一个操作中算法的骨架,希望一些步骤的执行推迟到子类中。模版模式涉及到两个角色,抽象模版和具体模版。抽象模版中定义了一个或多个操作,以便于让子类实现,这些操作是一个算法的逻辑和框架。以及一个模版方法,该模版方法是一个具体方法,它给出了抽象操作的实现逻辑。具体模版实现父类所定义的一个或多个抽象方法,它们是一个顶级逻辑的组成步骤。
使用场景:
1、模版方法模式广泛应用于框架设计(如Spring,Struts等)中,以保证父类控制处理路程的逻辑顺序,比如Spring对于Hibernate使用的简单封装:HibernateTemplate。
2、Java单元测试工具JUnit中的TestCase类的设计。
命令设计模式:
在面向对象的程序设计中,一个对象调用另一个对象,一般情况下的调用过程是:创建目标对象实例、设置调用参数、调用目标对象的方法。但是有些情况下有必要使用一个专门的类对这种调用过程加以封装,这种类叫做command类。
命令模式的本质是对命令进行封装,将发出命令的责任个执行命令的责任分隔开。每一个命令都是一个操作,请求的一方发出请求,要求执行一个动作,接收的一方收到请求,并执行操作。命令模式允许请求的一方和接收的一方独立开来,使得请求的一方不必知道接受请求的一方的接口,更不必知道请求是怎样被接收,以及操作是否被执行、何时被执行以及怎样被执行的。命令模式的关键在于引入了抽象命令接口,且发送者针对命令接口编程,只有实现了抽象命令接口的具体命令才能被接受者相关联。
命令模式包括5个角色:
命令角色,声明执行操作的接口,一般为接口或抽象类。
具体命令角色:将一个接收者对象绑定于一个动作,调用接收者相应的操作,以实现命令角色生命的执行操作的接口。
客户角色:创建一个具体命令对象,并可以设定它的接收者。
调用者角色:调用命令对象执行这个请求。
接收者角色:知道如何实施与执行一个请求相关的操作。任何类都可以作为一个接收者。
适用场景:现实中的遥控器。
中介者模式:
在面向对象的软件设计与开发过程中,根据单一职责原则,我们应该将对象细化,使其只负责或呈现单一的职责。对于一个模块,可能有很多对象构成,而且这些对象之间可能存在相互的引用,为了减少对象之间复杂的引用关系,使之成为一个松耦合的系统,我们需要使用中介者模式。
中介者模式包括抽象中介者、具体中介者、抽象同事类、具体同事类。
这里饿中介者有两方面的作用:中转作用。通过中转作用,各个同事对象不再需要显示的引用其他同事。同事之间通信时候通过中介者即可。协调作用。中介者可以更进一步的对同事之间的关系进行封装。
适用场景:
1、MVC 是Java EE 的一个基本模式,此时控制器Controller作为一种中介者,它负责控制视图对象View和模型对象Model之间的交互。如在Struts中,Action就可以作为JSP页面与业务对象之间的中介者。
2、QQ群原理。QQ群作为人与人之间的中介者,把人组织起来,负责人与人之间的交互。
状态模式:
很多情况下,一个对象的行为取决于一个或多个动态变化的属性,这样的属性叫做状态。当一个这样的对象与外部事件产生交互时,其内部状态就会改变,使得系统的行为也随之发生变化。
比如,以论坛用户等级为例。在这个论坛系统中,用户发表留言、回复留言增加积分;用户下载文件将扣除积分。假设将用户等级分为初级用户、中级用户和高级用户。如果用户积分小于100分,将处于初级用户状态,此时不能下载文件。如果积分大于100分但小于1000分,则为中级状态,用户可以下载文件,而且发表留言的时候可以获取双倍积分。若积分大于1000,不但发表留言获取双倍积分,而且下载文件只扣除一半积分。这种情况下,用户只需要进行正常的论坛操作,回复帖子、发帖子、下载等操作,系统会根据积分状态自动转换到相应的状态。
像这种,用户的行为随其等级不同而发生改变的场景可以使用状态模式。
状态模式包括环境类、抽象状态类、具体状态类。一般环境类中会关联一个状态类。
使用场景:
在政府OA办公系统中,一个批文的状态有多种:尚未办理;正在办理;正在批示;正在审核;已经完成等各种状态,而且批文状态不同时对批文的操作也有所差异。使用状态模式可以描述工作流对象(如批文)的状态转换以及不同状态下它所具有的行为。
策略模式:
完成一项任务,往往可以有多种不同的方式,每一种方式称为一个策略,我们可以根据环境或者条件的不同选择不同的策略来完成该项任务。在软件开发中也常常遇到这种情况,实现某个功能有多个路径,此时可以使用一种设计模式来使得系统可以灵活地选择解决途径,特能够方便的增加新的解决途径。
定义一系列算法,将每一个算法封装起来,并让它们可以相互替换。此模式让算法的变化不会影响到使用算法的用户。
策略模式包括环境类、抽象策略类、具体策略类。
迭代器模式:
一个聚合对象,例如一个列表(List)或者一个集合(Set)应该提供一种方法来让别人可以访问它的元素,而又不需要它的内部结构。针对不同的需要,可能还要以不同的方式遍历整个聚合对象,但是我们并不希望在聚合对象的抽象噌接口中充斥者各种不同遍历的操作。怎样一个聚合对象,又不需要了解聚合对象的内部结构,还能提供多种不同的遍历方式,即是迭代器的动机。
迭代器模式包括抽象聚合类、具体聚合类、抽象迭代器、具体迭代器。
想要了解更多设计模式、架构技术知识点的,可以关注我一下,我后续也会整理更多这一块的知识点分享出来,另外顺便给大家推荐一个交流学习群:650385180,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多,以下的课程体系图也是在群里获取。
总结:
以上就是我要说的内容,希望以上的内容可以帮助到正在默默艰辛,遇到瓶疾且不知道怎么办的Java程序员们,我能帮你的只有这么多了,希望大家在往后的工作与面试中,一切顺利。