快速梳理23种常用的设计模式(上)(创建型+行为型)

关注我

我目前是一名后端开发工程师。主要关注后端开发,数据安全,网络爬虫,物联网,边缘计算等方向。

原创博客主要内容

个人公众号:后端技术漫谈

本文旨在快速梳理常用的设计模式,了解每个模式主要针对的是哪些情况以及其基础特征,每个模式前都有列举出一个或多个可以深入阅读的参考网页,以供读者详细了解其实现。

快速回忆

一、创建型

二、行为型

三、结构型


理念

首先搞清楚一点,设计模式不是高深技术,不是奇淫技巧。设计模式只是一种设计思想,针对不同的业务场景,用不同的方式去设计代码结构,其最最本质的目的是为了解耦,延伸一点的话,还有为了可扩展性和健壮性,但是这都是建立在解耦的基础之上。

高内聚低耦合

高内聚:系统中A、B两个模块进行交互,如果修改了A模块,不影响模块B的工作,那么认为A有足够的内聚。

快速梳理23种常用的设计模式(上)(创建型+行为型)

低耦合:就是A模块与B模块存在依赖关系,那么当B发生改变时,A模块仍然可以正常工作,那么就认为A与B是低耦合的。

快速梳理23种常用的设计模式(上)(创建型+行为型)

创建型

单例模式

请参考Github详细解释,下面的一点仅供快速复习。Github写的很好。

同时参考:

http://blog.jobbole.com/109449/

意图

确保一个类只有一个实例,并提供该实例的全局访问点。

类图

使用一个私有构造函数、一个私有静态变量以及一个公有静态函数来实现。

私有构造函数保证了不能通过构造函数来创建对象实例,只能通过公有静态函数返回唯一的私有静态变量。

实现

懒汉式——线程安全/双重校验

单例是为了保证系统中只有一个实例,其关键点有

一.私有化构造函数

二.声明静态单例对象

三.构造单例对象之前要加锁(lock一个静态的object对象)或者方法上加synchronized。

四.需要两次检测单例实例是否已经被构造,分别在锁之前和锁之后

使用lock(obj)

使用synchronized (Singleton.class)

可能提问

0.为何要检测两次?

如果两个线程同时执行 if 语句,那么两个线程就会同时进入 if 语句块内。虽然在if语句块内有加锁操作,但是两个线程都会执行 uniqueInstance = new Singleton(); 这条语句,只是先后的问题,也就是说会进行两次实例化,从而产生了两个实例。因此必须使用双重校验锁,也就是需要使用两个 if 语句。

1.构造函数能否公有化?

不行,单例类的构造函数必须私有化,单例类不能被实例化,单例实例只能静态调用。

2.lock住的对象为什么要是object对象,可以是int吗?

不行,锁住的必须是个引用类型。如果锁值类型,每个不同的线程在声明的时候值类型变量的地址都不一样,那么上个线程锁住的东西下个线程进来会认为根本没锁。

3.uniqueInstance 采用 volatile 关键字修饰

uniqueInstance = new Singleton(); 这段代码其实是分为三步执行。

但是由于 JVM 具有指令重排的特性,有可能执行顺序变为了 1>3>2

饿汉式-线程安全

线程不安全问题主要是由于 uniqueInstance 被实例化了多次,如果 uniqueInstance 采用直接实例化的话,就不会被实例化多次,也就不会产生线程不安全问题。但是直接实例化的方式也丢失了延迟实例化带来的节约资源的优势。

静态内部类实现

当 Singleton 类加载时,静态内部类 SingletonHolder 没有被加载进内存。只有当调用 getUniqueInstance() 方法从而触发 SingletonHolder.INSTANCE 时 SingletonHolder 才会被加载,此时初始化 INSTANCE 实例。

这种方式不仅具有延迟初始化的好处,而且由虚拟机提供了对线程安全的支持。

枚举实现

这是单例模式的最佳实践,它实现简单,并且在面对复杂的序列化或者反射攻击的时候,能够防止实例化多次。

考虑以下单例模式的实现,该 Singleton 在每次序列化的时候都会创建一个新的实例,为了保证只创建一个实例,必须声明所有字段都是 transient,并且提供一个 readResolve() 方法。

如果不使用枚举来实现单例模式,会出现反射攻击,因为通过 setAccessible() 方法可以将私有构造函数的访问级别设置为 public,然后调用构造函数从而实例化对象。如果要防止这种攻击,需要在构造函数中添加防止实例化第二个对象的代码。

从上面的讨论可以看出,解决序列化和反射攻击很麻烦,而枚举实现不会出现这两种问题,所以说枚举实现单例模式是最佳实践。

使用场景

简单/静态工厂(Simple Factory)

https://www.jianshu.com/p/d1b6731c1c0e

定义

在创建一个对象时不向客户暴露内部细节,并提供一个创建对象的通用接口。

在简单工厂模式中,可以根据参数的不同返回不同类的实例

简单工厂模式专门定义一个类来负责创建其他类的实例

结构

简单工厂模式包含如下角色:

实现

优缺点

优点

构造容易,逻辑简单。

缺点

1.简单工厂模式中的if else判断非常多,完全是Hard Code,如果有一个新产品要加进来,就要同时添加一个新产品类,并且必须修改工厂类,再加入一个 else if 分支才可以, 这样就违背了 “开放-关闭原则“中的对修改关闭的准则了。

2.一个工厂类中集合了所有的类的实例创建逻辑,违反了高内聚的责任分配原则,将全部的创建逻辑都集中到了一个工厂类当中,所有的业务逻辑都在这个工厂类中实现。什么时候它不能工作了,整个系统都会受到影响。因此一般只在很简单的情况下应用,比如当工厂类负责创建的对象比较少时。

3.简单工厂模式由于使用了静态工厂方法,造成工厂角色无法形成基于继承的等级结构。

适用环境

JDK

①JDK类库中广泛使用了简单工厂模式,如工具类java.text.DateFormat,它用于格式化一个本地日期或者时间。

②Java加密技术
获取不同加密算法的密钥生成器:

创建密码器:

工厂方法(Factory Method)

https://www.jianshu.com/p/1cf9859e0f7c

意图

又称为工厂模式/虚拟构造器(Virtual Constructor)模式/多态工厂(Polymorphic Factory)模式

即通过工厂子类来确定究竟应该实例化哪一个具体产品类。

使用动机

不再设计一个按钮工厂类来统一负责所有产品的创建,而是将具体按钮的创建过程交给专门的工厂子类去完成。

我们先定义一个抽象的按钮工厂类,再定义具体的工厂类来生成圆形按钮、矩形按钮、菱形按钮等,它们实现在抽象按钮工厂类中定义的方法。这种抽象化的结果使这种结构可以在不修改具体工厂类的情况下引进新的产品,如果出现新的按钮类型,只需要为这种新类型的按钮创建一个具体的工厂类就可以获得该新按钮的实例,这一特点无疑使得工厂方法模式具有超越简单工厂模式的优越性,更加符合“开闭原则”。

角色

实现

看链接内

客户端调用

优缺点

优点

①在工厂方法模式中,工厂方法用来创建客户所需要的产品,同时还向客户隐藏了哪种具体产品类将被实例化这一细节,用户只需要关心所需产品对应的工厂,无须关心创建细节,甚至无须知道具体产品类的类名

工厂方法模式之所以又被称为多态工厂模式,是因为所有的具体工厂类都具有同一抽象父类

③使用工厂方法模式的另一个优点是在系统中加入新产品时,无须修改抽象工厂和抽象产品提供的接口,无须修改客户端,也无须修改其他的具体工厂和具体产品,而只要添加一个具体工厂和具体产品就可以了。这样,系统的可扩展性也就变得非常好,完全符合“开闭原则”,这点比简单工厂模式更优秀。

缺点

①在添加新产品时,需要编写新的具体产品类,而且还要提供与之对应的具体工厂类,系统中类的个数将成对增加,在一定程度上增加了系统的复杂度,有更多的类需要编译和运行,会给系统带来一些额外的开销。

②由于考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度,且在实现时可能需要用到DOM、反射等技术,增加了系统的实现难度。

抽象工厂(Abstract Factory)

https://www.jianshu.com/p/d6622f3e71ed

定义

产品等级结构 :产品等级结构即产品的继承结构,如一个抽象类是电视机,其子类有海尔电视机、海信电视机、TCL电视机,则抽象电视机与具体品牌的电视机之间构成了一个产品等级结构,抽象电视机是父类,而具体品牌的电视机是其子类。

产品族 :在抽象工厂模式中,产品族是指由同一个工厂生产的,位于不同产品等级结构中的一组产品,如海尔电器工厂生产的海尔电视机、海尔电冰箱,海尔电视机位于电视机产品等级结构中,海尔电冰箱位于电冰箱产品等级结构中。

抽象工厂模式是所有形式的工厂模式中最为抽象和最具一般性的一种形态。

抽象工厂模式与工厂方法模式最大的区别

工厂方法模式针对的是一个产品等级结构,而抽象工厂模式则需要面对多个产品等级结构,一个工厂等级结构可以负责多个不同产品等级结构中的产品对象的创建 。

角色

抽象工厂模式包含如下角色:

实现

抽象产品: 苹果系列

具体产品:iphone

抽象工厂

手机工厂

调用

优缺点

优点

①应用抽象工厂模式可以实现高内聚低耦合的设计目的,因此抽象工厂模式得到了广泛的应用。

增加新的具体工厂和产品族很方便,因为一个具体的工厂实现代表的是一个产品族,无须修改已有系统,符合“开闭原则”。

缺点

开闭原则的倾斜性(增加新的工厂和产品族容易,增加新的产品等级结构麻烦

适用环境

在以下情况下可以使用抽象工厂模式:

①一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节,这对于所有类型的工厂模式都是重要的。

②系统中有多于一个的产品族,而每次只使用其中某一产品族。(与工厂方法模式的区别)

③属于同一个产品族的产品将在一起使用,这一约束必须在系统的设计中体现出来。

④系统提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于具体实现。

生成器(Builder)

https://blog.csdn.net/c275046758/article/details/50540789

意图

封装一个对象的构造过程,并允许按步骤构造。

实现

快速梳理23种常用的设计模式(上)(创建型+行为型)

原型模式(Prototype)

https://www.cnblogs.com/lwbqqyumidi/p/3746821.html

意图

通过一个已经存在的对象,复制出更多的具有与此对象具有相同类型的新的对象。

浅复制 clone():

我们知道,一个类的定义中包括属性和方法。属性用于表示对象的状态,方法用于表示对象所具有的行为。其中,属性既可以是Java中基本数据类型,也可以是引用类型。

Java中的浅复制通常使用clone()方式完成。

当进浅复制时,clone函数返回的是一个引用,指向的是新的clone出来的对象,此对象与原对象分别占用不同的堆空间。同时,复制出来的对象具有与原对象一致的状态。

此处对象一致的状态是指:复制出的对象与原对象中的属性值完全相等==。

深复制 deepclone():

Java中的深复制一般是通过对象的序列化和反序列化得以实现。序列化时,需要实现Serializable接口。

从输出结果中可以看出,深复制不仅在堆内存上开辟了空间以存储复制出的对象,甚至连对象中的引用类型的属性所指向的对象也得以复制,重新开辟了堆空间存储。

行为型

责任链(Chain Of Responsibility)

https://blog.csdn.net/maoyuanming0806/article/details/80183494

意图

使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链发送该请求,直到有一个对象处理它为止。

在这种模式中,通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。

应用实例

红楼梦中的"击鼓传花"。 JS 中的事件冒泡。 JAVA WEB 中 Apache Tomcat 对 Encoding 的处理,Struts2 的拦截器,jsp servlet 的 Filter。

何时使用:在处理消息的时候以过滤很多道。

如何解决:拦截的类都实现统一接口。

关键代码:Handler 里面聚合它自己,在 HandlerRequest 里判断是否合适,如果没达到条件则向下传递,向谁传递之前 set 进去。

命令(Command)

https://www.cnblogs.com/konck/p/4199907.html

意图

命令模式是为了解决命令的请求者和命令的实现者之间的耦合关系。

解决了这种耦合的好处我认为主要有两点:

1.更方便的对命令进行扩展(注意:这不是主要的优势,后面会提到)

2.对多个命令的统一控制(这种控制包括但不限于:队列、撤销/恢复、记录日志等等)

应用实例

struts 1 中的 action 核心控制器 ActionServlet 只有一个,相当于 Invoker,而模型层的类会随着不同的应用有不同的模型类,相当于具体的 Command。

类图

解释器(Interpreter)

https://www.cnblogs.com/chenssy/p/3346427.html

意图

所谓解释器模式就是定义语言的文法,并且建立一个解释器来解释该语言中的句子。

这种模式实现了一个表达式接口,该接口解释一个特定的上下文。这种模式被用在 SQL 解析、符号处理引擎等。

应用实例

编译器、运算表达式计算。

迭代器(Iterator)

https://www.jianshu.com/p/3d0406a01b73

意图

提供一种顺序访问聚合对象元素的方法,并且不暴露聚合对象的内部表示。

中介者(Mediator)

http://www.runoob.com/design-pattern/mediator-pattern.html

意图

集中相关对象之间复杂的沟通和控制方式。

实现

Alarm(闹钟)、CoffeePot(咖啡壶)、Calendar(日历)、Sprinkler(喷头)是一组相关的对象,在某个对象的事件产生时需要去操作其它对象,形成了下面这种依赖结构:

使用中介者模式可以将复杂的依赖结构变成星形结构:

[图片上传失败…(image-93800a-1542466746560)]

[图片上传失败…(image-38baf0-1542466746560)]

备忘录(Memento)

https://blog.csdn.net/o279642707/article/details/60767258

意图

在不违反封装的情况下获得对象的内部状态,从而在需要时可以将对象恢复到最初状态。

主要解决

所谓备忘录模式就是在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。

应用实例

1、后悔药。 2、打游戏时的存档。 3、Windows 里的 ctri + z。 4、IE 中的后退。 4、数据库的事务管理。

JDK

观察者(Observer)

https://www.jianshu.com/p/fc4554cda68d

意图

观察者模式是对象的行为模式,又叫发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。

观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态上发生变化时,会通知所有观察者对象,使它们能够自动更新自己。

[图片上传失败…(image-f45dc1-1542466746560)]

何时使用

一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。

状态(State)

https://www.cnblogs.com/java-my-life/archive/2012/06/08/2538146.html

意图

允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它所属的类。

应用实例

考虑一个在线投票系统的应用,要实现控制同一个用户只能投一票,如果一个用户反复投票,而且投票次数超过5次,则判定为恶意刷票,要取消该用户投票的资格,当然同时也要取消他所投的票;如果一个用户的投票次数超过8次,将进入黑名单,禁止再登录和使用系统。

策略(Strategy)

https://www.jianshu.com/p/7fa8ad000a97

https://www.cnblogs.com/wenjiang/p/3352041.html

意图

定义一系列算法,封装每个算法,并使它们可以互换。

策略模式和状态模式的区别:

之所以说状态模式是策略模式的孪生兄弟,是因为它们的UML图是一样的,但意图却完全不一样,策略模式是让用户指定更换的策略算法,而状态模式是状态在满足一定条件下的自动更换,用户无法指定状态,最多只能设置初始状态。

策略模式可以让算法独立于使用它的客户端。

具体场景实现

假设现在要设计一个贩卖各类书籍的电子商务网站的购物车系统。一个最简单的情况就是把所有货品的单价乘上数量,但是实际情况肯定比这要复杂。比如,本网站可能对所有的高级会员提供每本20%的促销折扣;对中级会员提供每本10%的促销折扣;对初级会员没有折扣。

根据描述,折扣是根据以下的几个算法中的一个进行的:

算法一:对初级会员没有折扣。

算法二:对中级会员提供10%的促销折扣。

算法三:对高级会员提供20%的促销折扣。

策略模式对多态的使用

通过让环境类持有一个抽象策略类(超类)的引用,在生成环境类实例对象时,让该引用指向具体的策略子类。再对应的方法调用中,就会通过Java的多态,调用对应策略子类的方法。从而可以相互替换,不需要修改环境类内部的实现。同时,在有新的需求的情况下,也只需要修改策略类即可,降低与环境类之间的耦合度。

策略模式和工厂方法的异同

工厂模式和策略模式的区别在于实例化一个对象的位置不同,对工厂模式而言,实例化对象是放在服务端的,即放在了工厂类里面; 而策略模式实例化对象的操作在客户端

工厂模式要求服务端的销售部门足够灵敏,而策略模式由于对策略进行了封装,所以他的销售部门比较傻,需要客户提供足够能区分使用哪种策略的参数,而这最好的就是该策略的实例了。

JDK

模板方法(Template Method)

https://www.jianshu.com/p/cc391b56bd0e

典型用例:Spring

定义

模板方法模式是类的行为模式。

准备一个抽象类,将部分逻辑以具体方法以及具体构造函数的形式实现,然后声明一些抽象方法来迫使子类实现剩余的逻辑。不同的子类可以以不同的方式实现这些抽象方法,从而对剩余的逻辑有不同的实现。这就是模板方法模式的用意。

结构

模板方法中的方法可以分为两大类:模板方法和基本方法。

模板方法

一个模板方法是定义在抽象类中的,把基本操作方法组合在一起形成一个总算法或一个总行为的方法。

一个抽象类可以有任意多个模板方法,而不限于一个。每一个模板方法都可以调用任意多个具体方法。

基本方法

基本方法又可以分为三种

默认钩子方法

一个钩子方法常常由抽象类给出一个空实现作为此方法的默认实现。这种空的钩子方法叫做“Do Nothing Hook”。具体模版类中可以选择是否重写钩子方法,通常重写钩子方法是为了对模版方法中的步骤进行控制,判断钩子方法中的状态,是否进行下一步操作。

使用场景

模板方法模式是基于继承的代码复用技术,它体现了面向对象的诸多重要思想,是一种使用较为频繁的模式。模板方法模式广泛应用于框架设计中,以确保通过父类来控制处理流程的逻辑顺序(如框架的初始化,测试流程的设置等)。

JDK

访问者(Visitor)

https://www.jianshu.com/p/80b9cd7c0da5

什么是访问者模式?

比如我有一个账单,账单有收入,支出两个固定方法。但是访问账单的人不确定,有可能是一个或者多个。

访问者模式有两个特点

一般被访问的东西所持有的方法是固定的,就像账单只有收入和支出两个功能。而访问者是不固定的。

数据操作与数据结构相分离:频繁的更改数据,但不结构不变。比如:虽然每一天账单的数据都会变化(数据变化),但是只有两类数据,就是支出和收入(结构不变)。

代码

见参考网页

结构型请看下篇!

参考

简书大牛:https://www.jianshu.com/nb/10186551

Github:https://github.com/CyC2018/Interview-Notebook/blob/master/notes/设计模式.md

菜鸟网:http://www.runoob.com/design-pattern/design-pattern-tutorial.html

补充:

23种设计模式总结:

https://www.cnblogs.com/tongkey/p/7170826.html

https://www.cnblogs.com/malihe/p/6891920.html

展开阅读全文

页面更新:2024-05-22

标签:品族   模式   子类   意图   抽象   实例   角色   对象   工厂   状态   策略   常用   快速   结构   方法   数码   系统   产品

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2020-2024 All Rights Reserved. Powered By 71396.com 闽ICP备11008920号-4
闽公网安备35020302034903号

Top