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

简介: 本文旨在快速梳理常用的设计模式,了解每个模式主要针对的是哪些情况以及其基础特征,每个模式前都有列举出一个或多个可以深入阅读的参考网页,以供读者详细了解其实现。分为三篇文章:上篇:设计模式基础理念和创建型设计模式中篇:行为型设计模式下篇:结构型设计模式


前言



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

分为三篇文章:

  • 上篇:设计模式基础理念和创建型设计模式
  • 中篇:行为型设计模式
  • 下篇:结构型设计模式


快速回忆



行为型

  • 责任链(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是低耦合的。


行为型



意图

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

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


职责和角色

  • 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


意图

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

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

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

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


应用实例

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


类图

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


JDK


解释器(Interpreter)



意图

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

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


应用实例

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


JDK


迭代器(Iterator)



意图

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


迭代器模式的优缺点


优点

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

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

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


缺点

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


JDK


中介者(Mediator)



意图

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


实现

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

网络异常,图片无法展示
|

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

网络异常,图片无法展示
|


JDK


备忘录(Memento)



意图

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


主要解决

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


应用实例

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


JDK

  • java.io.Serializable


观察者(Observer)



意图

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

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

网络异常,图片无法展示
|


何时使用

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


实现举例

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

网络异常,图片无法展示
|


JDK


状态(State)



意图

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


应用实例

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


策略(Strategy)



意图

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

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

之所以说状态模式是策略模式的孪生兄弟,是因为它们的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)



定义

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

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


结构

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


模板方法

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

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


基本方法

基本方法又可以分为三种

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

默认钩子方法

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


使用场景

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


JDK

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


访问者(Visitor)



什么是访问者模式?

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


访问者模式有两个特点

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

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


空对象(Null)



意图

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

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

相关文章
|
设计模式 关系型数据库
【设计模式——学习笔记】设计模式简介+七大设计原则介绍(下)
【设计模式——学习笔记】设计模式简介+七大设计原则介绍
119 0
|
3月前
|
设计模式 Java 关系型数据库
设计模式——设计模式简介和七大原则
设计模式的目的和核心原则、单一职责原则、接口隔离原则、依赖倒转原则、里氏替换原则、开闭原则、迪米特法则、合成复用原则
设计模式——设计模式简介和七大原则
|
7月前
|
设计模式 XML Java
第五篇 设计模式的选择和应用 - 智慧选择与合理实践
第五篇 设计模式的选择和应用 - 智慧选择与合理实践
|
6月前
|
设计模式 存储 前端开发
清晰理解基本的架构设计模式
【6月更文挑战第16天】广泛应用在如Spring、Merb、Django和.NET MVC等框架中。核心价值在于组件解耦,适合插件化和Web应用。尽管有变体,如Django的MTV,关键在于明确的分离。[1978原文][1] | [IBM文档][2]
77 8
清晰理解基本的架构设计模式
|
7月前
|
设计模式 自然语言处理 算法
自己理解设计模式,梳理一下
自己理解设计模式,梳理一下
74 1
|
设计模式 自然语言处理 算法
一篇文章读懂六大设计模式
一篇文章读懂六大设计模式
103 1
|
设计模式 Java 程序员
【设计模式——学习笔记】设计模式简介+七大设计原则介绍(上)
【设计模式——学习笔记】设计模式简介+七大设计原则介绍
54 2
|
设计模式 算法
设计模式一句话概括
设计模式一句话概括
52 0
|
设计模式 存储 算法
23个小案例带你吃透23种设计模式(二)
23个小案例带你吃透23种设计模式
158 0
23个小案例带你吃透23种设计模式(二)
|
设计模式 算法 搜索推荐
23个小案例带你吃透23种设计模式(三)
23个小案例带你吃透23种设计模式
163 0
23个小案例带你吃透23种设计模式(三)