专栏首页后端技术漫谈快速梳理常用的设计模式(中篇)

快速梳理常用的设计模式(中篇)

前言

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

分为三篇文章:

面试知识点复习手册

全复习手册文章导航

点击公众号下方:技术推文——面试冲刺

快速回忆

行为型

  • 责任链(Chain Of Responsibility)
  • 命令(Command)
  • 解释器(Interpreter)
  • 迭代器(Iterator)
  • 中介者(Mediator)
  • 备忘录(Memento)
  • 观察者(Observer)
  • 状态(State)
  • 策略(Strategy)
  • 模板方法(Template Method)
  • 访问者(Visitor)
  • 空对象(Null)

理念

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

高内聚低耦合

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

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

行为型

责任链(Chain Of Responsibility)

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

意图

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

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

职责和角色

  • Handler:处理类的抽象父类
  • concreteHandler:具体的处理类

设计使用责任链的基本流程

  • 组织对象链:将某人物的所有职责执行对象以链的形式加以组织起来
  • 消息或请求的传递:将消息或请求沿着对象链传递,让处于对象链中的对象得到处理机会
  • 对象链中对象的职责分配:不同对象完成不同职责
  • 任务的完成:对象链末尾的对象结束任务并停止消息或请求的继续传递。

应用实例

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

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

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

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

实现举例

在这里插入图片描述

处理器的抽象类

package com.mym.designmodel.CoRModel;

/**
 * 职责:Handler 职责类的抽象父类
 */
public abstract class AbstractCarHandler {

    AbstractCarHandler carHandler = null;

    public abstract void carHandler();

    public AbstractCarHandler setNextCarHandler(AbstractCarHandler nextCarHandler){
        this.carHandler = nextCarHandler;
        return this.carHandler;
    }

    /**职责下传*/
    protected void doChain(){
        if(this.carHandler != null){
            this.carHandler.carHandler();
        }
    }
}

责任链一个执行者1

package com.mym.designmodel.CoRModel;

/**
 * 职责:concreteHandler 具体的处理类
 */
public class CarHeadHandler extends AbstractCarHandler {
    @Override
    public void carHandler() {
        System.out.println("处理车的head!");

        //下传
        this.doChain();
    }
}

责任链一个执行者2

package com.mym.designmodel.CoRModel;

/**
 * 职责:concreteHandler 具体的处理类
 */
public class CarBodyHandler extends AbstractCarHandler {
    @Override
    public void carHandler() {
        System.out.println("处理车的body!");

        //下传
        this.doChain();
    }
}

责任链一个执行者3

package com.mym.designmodel.CoRModel;

/**
 * 职责:concreteHandler 具体的处理类
 */
public class CarTailHandler extends AbstractCarHandler {
    @Override
    public void carHandler() {
        System.out.println("处理车的tail!");

        //下传
        this.doChain();
    }
}

客户端client

package com.mym.designmodel.CoRModel;

/**
 * 测试
 */
public class MainClass {

    public static void main(String[] args) {
        AbstractCarHandler carheadHandle = new CarHeadHandler();
        AbstractCarHandler carbodyHandle = new CarBodyHandler();
        AbstractCarHandler carTailHandler = new CarTailHandler();

        //组装责任链
        carheadHandle.setNextCarHandler(carbodyHandle).setNextCarHandler(carTailHandler);

        //链头部开始执行
        carheadHandle.carHandler();
    }
}

JDK

  • java.util.logging.Logger#log()
  • Apache Commons Chain
  • javax.servlet.Filter#doFilter()

命令(Command)

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

意图

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

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

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

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

应用实例

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

类图

  • Command:命令
  • Receiver:命令接收者,也就是命令真正的执行者
  • Invoker:通过它来调用命令
  • Client:可以设置命令与命令的接收者

JDK

  • java.lang.Runnable
  • Netflix Hystrix
  • javax.swing.Action

解释器(Interpreter)

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

意图

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

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

应用实例

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

JDK

  • java.util.Pattern
  • java.text.Normalizer
  • All subclasses of java.text.Format
  • javax.el.ELResolver

迭代器(Iterator)

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

意图

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

迭代器模式的优缺点

优点

①简化了遍历方式,对于对象集合的遍历,还是比较麻烦的,对于数组或者有序列表,我们尚可以通过游标来取得,但用户需要在对集合了解很清楚的前提下,自行遍历对象,但是对于hash表来说,用户遍历起来就比较麻烦了。而引入了迭代器方法后,用户用起来就简单的多了。

②可以提供多种遍历方式,比如说对有序列表,我们可以根据需要提供正序遍历,倒序遍历两种迭代器,用户用起来只需要得到我们实现好的迭代器,就可以方便的对集合进行遍历了。

③封装性良好,用户只需要得到迭代器就可以遍历,而对于遍历算法则不用去关心。

缺点

对于比较简单的遍历(像数组或者有序列表),使用迭代器方式遍历较为繁琐,大家可能都有感觉,像ArrayList,我们宁可愿意使用for循环和get方法来遍历集合。

JDK

  • java.util.Iterator
  • java.util.Enumeration

中介者(Mediator)

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

意图

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

实现

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

在这里插入图片描述

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

在这里插入图片描述

JDK

  • All scheduleXXX() methods of java.util.Timer
  • java.util.concurrent.Executor#execute()
  • submit() and invokeXXX() methods of java.util.concurrent.ExecutorService
  • scheduleXXX() methods of java.util.concurrent.ScheduledExecutorService
  • java.lang.reflect.Method#invoke()

备忘录(Memento)

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

意图

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

主要解决

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

应用实例

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

JDK

  • java.io.Serializable

观察者(Observer)

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

意图

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

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

在这里插入图片描述

何时使用

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

实现举例

天气数据布告板会在天气信息发生改变时更新其内容,布告板有多个,并且在将来会继续增加。

在这里插入图片描述

JDK

  • java.util.Observer
  • java.util.EventListener
  • javax.servlet.http.HttpSessionBindingListener
  • RxJava

状态(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%的促销折扣。

public static void main(String[] args) {
        //选择并创建需要使用的策略对象
        MemberStrategy strategy = new AdvancedMemberStrategy();
        //创建环境
        Price price = new Price(strategy);
        //计算价格
        double quote = price.quote(300);
        System.out.println("图书的最终价格为:" + quote);
    }

策略模式对多态的使用

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

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

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

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

JDK

  • java.util.Comparator#compare()
  • javax.servlet.http.HttpServlet
  • javax.servlet.Filter#doFilter()

模板方法(Template Method)

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

典型用例:Spring

定义

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

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

结构

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

模板方法

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

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

基本方法

基本方法又可以分为三种

  • 抽象方法:一个抽象方法由抽象类声明,由具体子类实现。在Java语言里抽象方法以abstract关键字标示。
  • 具体方法:一个具体方法由抽象类声明并实现,而子类并不实现或置换。
  • 钩子方法:一个钩子方法由抽象类声明并实现,而子类会加以扩展。通常抽象类给出的实现是一个空实现,作为方法的默认实现。

默认钩子方法

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

使用场景

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

JDK

  • java.util.Collections#sort()
  • java.io.InputStream#skip()
  • java.io.InputStream#read()
  • java.util.AbstractList#indexOf()

访问者(Visitor)

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

什么是访问者模式?

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

访问者模式有两个特点

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

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

代码

见参考网页

空对象(Null)

意图

使用什么都不做的空对象来代替 NULL。

一个方法返回 NULL,意味着方法的调用端需要去检查返回值是否是 NULL,这么做会导致非常多的冗余的检查代码。并且如果某一个调用端忘记了做这个检查返回值,而直接使用返回的对象,那么就有可能抛出空指针异常。

-----正文结束-----

更多精彩文章,请查阅我的博客或关注我的公众号:Rude3Knife

全复习手册文章导航

点击公众号下方:技术推文——面试冲刺

全复习手册文章导航(CSDN)

知识点复习手册文章推荐

关注我

我是蛮三刀把刀,目前为后台开发工程师。主要关注后台开发,网络安全,Python爬虫等技术。

来微信和我聊聊:yangzd1102

Github:https://github.com/qqxx6661

原创博客主要内容

  • 笔试面试复习知识点手册
  • Leetcode算法题解析(前150题)
  • 剑指offer算法题解析
  • Python爬虫相关技术分析和实战
  • 后台开发相关技术分析和实战

同步更新以下博客

1. Csdn

http://blog.csdn.net/qqxx6661

拥有专栏:

  • Leetcode题解(Java/Python)
  • Python爬虫实战
  • Java程序员知识点复习手册

2. 知乎

https://www.zhihu.com/people/yang-zhen-dong-1/

拥有专栏:

  • Java程序员面试复习手册
  • LeetCode算法题详解与代码实现
  • 后台开发实战

3. 掘金

https://juejin.im/user/5b48015ce51d45191462ba55

4. 简书

https://www.jianshu.com/u/b5f225ca2376

个人公众号:Rude3Knife

如果文章对你有帮助,不妨收藏起来并转发给您的朋友们~

本文分享自微信公众号 - Rude3Knife(Rude3Knife)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-02-28

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 6大设计原则之接口隔离原则

    如果有一天,他不在教书了,或者又有了新的职业,那我们还要修改调用该类的代码,更好的做法是将臃肿的接口变更为几个独立的接口

    烟草的香味
  • 【Jedis配置】springSSM + Jedis连接池配置

    当一个类实现了Serializable接口(该接口仅为标记接口,不包含任何方法定义),表示该类可以序列化.序列化的目的是将一个实现了Serializable接...

    用户5640963
  • Android利用SurfaceView显示Camera图像爬坑记(六) -- 用OpenCV进行Canny边缘检测

    上一篇《Android利用SurfaceView显示Camera图像爬坑记(五) -- 在现有项目中加入NDK配置》中我们已经把NDK加入到SurfaceVie...

    Vaccae
  • 高并发Java(2):多线程基础

    使用线程的原因是,进程的切换是非常重量级的操作,非常消耗资源。如果使用多进程,那么并发数相对来说不会很高。而线程是更细小的调度单元,更加轻量级,所以线程会较为广...

    用户5640963
  • Android NDK编程(一)---NDK介绍及环境搭建

    NDK全称为Native Development Kit,是Android的一个工具开发包,帮助开发者快速开发C(或C++)的动态库,并能自动将so和java应...

    Vaccae
  • Intellij IDEA 智能补全的 10 个姿势,简直不能太牛逼!

    一年多前,栈长那时候刚从 Eclipse 转型 IDEA 成功,前面转了好多次,都是失败史,都是泪。。

    Java技术栈
  • 【spring源码解析】spring源码报错问题

    下载spring5.0.4源码 用gradle 编译生成了 spring-jcl spring-core spring-beans 3个eclipse 项目 ...

    用户5640963
  • 23种设计模式之单例模式

    该单例模式在低并发的情况下可能不会出现问题,若并发量增加就可能在内存中出现多个实例,破坏了最初的预期.

    烟草的香味
  • c3p0之DEBUG -- CLOSE BY CLIENT STACK TRACE

    本文转载自:http://blog.csdn.net/rchm8519/article/details/40147745

    用户5640963
  • 高并发Java(3):Java内存模型和线程安全

    网上很多资料在描述Java内存模型的时候,都会介绍有一个主存,然后每个工作线程有自己的工作内存。数据在主存中会有一份,在工作内存中也有一份。工作内存和主存之间会...

    用户5640963

扫码关注云+社区

领取腾讯云代金券