Java设计模式之行为型模式

1 设计模式简介

软件设计模式是一套被反复使用的,多数人知晓的、经过分类编写、代码设计经验的总结。它描述了在软件设计过程中的一些不断重复发生的问题,以及该问题的解决方案,它是解决特定问题的一系列套路,是前辈们代码设计经验的总结,具有一定的普遍性,可以反复使用。软件设计模式的目的是为了提高代码的可重用性,代码的可读性和代码的可靠性。

1.1 设计模式和原则

一个具有良好结构的设计具备如下基本的特征。
在这里插入图片描述

1.2 学习设计模式的意义

设计模式的本质是面向对象设计原则的实际运用,是对类的封装性、继承性和多态性以及类的关联关系和组合关系的充分理解。正确使用设计模式具有以下优点:

  1. 可以提高程序员的思维能力、编程能力和设计能力。
  2. 使程序设计更加标准化、代码编制更加工程化,使软件开发效率大大提高,从而缩短软件的开发周期。
  3. 使设计的代码可重用性高、可读性强、可靠性高、灵活性好、可维护性强。
  4. 可以大大增强代码的质量,在提升代码结构的同时,使得代码性能的优化也成为了可能。

2 行为型模式组成

在阅读Java设计模式中,我曾经摘抄道:“世界上80%的人,都在默默无闻中渡过自己的一辈子,都在抱怨中过着每天的日子,都在对社会以及对周围的亲人和朋友不满足来打发日志。”自己也是一个喜欢抱怨的人,经常抱怨自己的工作、抱怨身边的人, 抱怨真的很可怕。曾国藩曾经说过:“牢骚太甚者,其后必多抑塞。盖无故而怨天,则天必不许,无故而尤人,则人必不服,感应之理然也。”抱怨太多,就是折磨自己。而且抱怨解决不了问题。与其抱怨,不如改变。有时间抱怨,不如想办法改变,让自己摆脱困境,很多时候,我们虽然无法改变已经发生的事实,但是我们可以转变自己的心态。
2.1 行为型设计模式的特征
在这里插入图片描述

2.2 种类

在Java设计模式及实践中,一共有12种设计模式的实践属于此类。
在这里插入图片描述
由于一些较为常见的设计模式已经习得,便不再此处赘述简要阐述,在之后的行文中,主要阐述如下的行为型设计模式:

  1. 命令模式
  2. 解释器模式
  3. 中介者模式
  4. 备忘录模式
  5. 状态模式
  6. 策略模式
  7. 访问者模式

2.3 行为型设计模式详解

2.3.1 责任链模式

责任链,通过其名称,我们可以认识到,有一个对象的链的存在,通过链表的方式串联起来,而请求的处理则是沿着这条链进行,直到有一个对象能处理这个请求。
使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,
并沿着这条链传递该请求,直到有一个对象处理它为止。

这一模式的想法是,给多个对象处理一个请求的机会,从而解耦发送者和接受者.

2.3.1.1 参与者

  1. Handler
    定义一个处理请求的接口。
    (可选)实现后继链。

  2. ConcreteHandler
    处理它所负责的请求。
    可访问它的后继者。
    如果可处理该请求,就处理之;否则将该请求转发给它的后继者。

  3. Client
    向链上的具体处理者(ConcreteHandler)对象提交请求。在实际使用中,由Client负责对象链的构建,实例化一个处理器的链,然后在第一个对象中调用handleRequest,其实如果想要实现的简单一点,只要把具体处理者放在一个列表中,然后让请求轮流走过找到合适的处理着直到请求得到处理也行的。

2.3.1.2 UML类图

在这里插入图片描述
在具体的处理器类中有如下的逻辑:

protected Handler successor;
public void setSuccessor(Successor successor) {
    this.successor = successor;
}

public void handleRequest(Request request) {
    if (canHandle(request)) {
        // code to handle the request
    } else {
        successor.handleRequest()
    }   
}

2.3.1.3 参见

责任链模式之实现

2.3.2 命令模式

将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤消的操作。

2.3.2.1 适用性

1.抽象出待执行的动作以参数化某对象。
2.在不同的时刻指定、排列和执行请求。
3.支持撤销和重做操作操作。
4.支持修改日志,这样当系统崩溃时,这些修改可以被重做一遍。
5.用构建在原语操作上的高层操作构造一个系统。
6.异步方法调用。

2.3.2.2 UML

在这里插入图片描述

2.3.2.3 实践

直观的想法

public void performAction(ActionEvent e) {
    Object obj = e.getSource();
    if (obj = fileNewNameMenuItem) {
        doFileNewAction();
    } else if (obj = fileOpenMenuItem) {
        doFileOpenAction();
    } else if (obj = fileOpenRecentMenuItem) {
        doFileOpenRecentMenuAction() 
    } else {
        doFileSaveAction();
    }
}

上图是用来处理一个客户端菜单的按钮的处理逻辑。起初想法是在一个大的if-else中处理所有可能出现的命令。
之后决定进行如下的修改,使用命令模式:

public interface Command {
    public void execute();
}

public class OpenMenuItem extends JMenuItem implements Command {
    public void execute() {
        // code to open a document
    }
}

publicvoid performAction(ActionEvent e) {
    Command command = (Command)e.getSource();
    command.execute();
}

可以看到代码消灭了冗长的if-else,使得代码更加紧凑,可读性也更突出。
在《重构2》中,Martin也阐述了使用命令模式来取代函数的用法。可以学习。

2.3.2.4 参考

命令模式实践

2.3.3 解释器模式

这种模式主要是用来解释句子或表达式。首先要知道的是句子和表达式的结构,要有一个表达式或句子的内部表示。可以使用解释器模式来处理逆波兰表达式
在这里插入图片描述
解释器模式一般使用组合模式来定义对象结构的内部表示。

2.3.3.1 UML类图

在这里插入图片描述

2.3.3.2 参与者

  1. AbstractExpression(抽象表达式)
    声明一个抽象的解释操作,这个接口为抽象语法树中所有的节点所共享。
  2. TerminalExpression(终结符表达式)
    实现与文法中的终结符相关联的解释操作。
    一个句子中的每个终结符需要该类的一个实例。
  3. NonterminalExpression(非终结符表达式)
    为文法中的非终结符实现解释(Interpret)操作。
  4. Context(上下文)
    包含解释器之外的一些全局信息。
  5. Client(客户)
    构建(或被给定)表示该文法定义的语言中一个特定的句子的抽象语法树。
    该抽象语法树由NonterminalExpression和TerminalExpression的实例装配而成。
    调用解释操作。
    在实现时,可以定义Expression表达式,然后让所有的节点均实现该接口。
public interface Expression {
    public void interpret();
}

public class Number implements Expression {
    private float number;
    public Number(float number) {
        this.number = number;
    }
    
    public void interpret() {
        return number;
    }
}

public class Plus implements Expression {
    Expression left;
    Expression right;
    public void interpret() {
        return left.interpret() + right.interpret();
    }
}

因此,通过这些类型我们建立了一课语法树:操作是节点,变量和数字是叶子。结构非常复杂,可用于解释表达式。

2.3.4 迭代器模式

提供一种方法顺序遍历对象元素而不暴露其内部实现的方法。

2.3.4.1 UML类图

其实迭代器非常常用,几乎每天都要使用。

public interface Iterator
{
    public Object next();
    public boolean hasNext();    
}

只不过要在实现的容器中实现该迭代器接口,以某种策略遍历容器中的元素。

2.3.4.2 实践

迭代器的理解与实现

2.3.5 观察者模式

我们不断提到解耦的重要性,当减少依赖时,我们可以扩展、开发和测试不同的模块,而无须了解其他模块的实现细节。
观察者模式定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

2.3.5.1 UML

在这里插入图片描述

2.3.5.2 参与者

  1. Subject(目标)
    目标知道它的观察者。可以有任意多个观察者观察同一个目标。
    提供注册和删除观察者对象的接口。

  2. Observer(观察者)
    为那些在目标发生改变时需获得通知的对象定义一个更新接口。

  3. ConcreteSubject(具体目标)
    将有关状态存入各ConcreteObserver对象。
    当它的状态发生改变时,向它的各个观察者发出通知。

  4. ConcreteObserver(具体观察者)
    维护一个指向ConcreteSubject对象的引用。
    存储有关状态,这些状态应与目标的状态保持一致。
    实现Observer的更新接口以使自身状态与目标的状态保持一致

2.3.5.3 实践

观察者模式

2.3.6 中介者模式

本质是解耦了多个同事之间的关系,每个对象都有中介者对象的引用,只跟中介者对象打交道,我们通过中介者对象统一管理这些交互关系。
即同事与同事之间并不交互,而每个同事都保存了一个中介者对象,由该对象与其他同事进行交互。
用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。

2.3.6.1 UML

在这里插入图片描述

2.3.6.2 参与者

  1. Mediator
    中介者定义一个接口用于与各同事(Colleague)对象通信。

  2. ConcreteMediator
    具体中介者通过协调各同事对象实现协作行为。
    了解并维护它的各个同事。

  3. Colleagueclass
    每一个同事类都知道它的中介者对象。
    每一个同事对象在需与其他的同事通信的时候,与它的中介者通信

2.3.6.3 实践

参见GOF23设计模式之中介者模式的实现
在跨部门协作时,比如与算法组沟通,要开发一个新的算法,我们是要与算法组长沟通的,而不会直接和算法组具体的某个人进行沟通的。此时组长,就相当于一个中介者。

public interface Mediator { 
    public void register(String dname, Deparment d);
    public void command(String dname);
 }   
 /** * 时间:2015年4月12日09:59:50 
 * 抽象同事类:抽象出所有部门的共同之处。 
 *  */
 package com.bjsxt.cn.mediator;
 public interface Deparment {
     public void selfAction(); 
     public void outAction();
}

2.3.7 备忘录模式

封装是面向对象设计的基本原则之一。我们知道类都承担一项职责。当向对象添加功能时,我们可能意识到需要保存其内部状态,以便能够在以后阶段恢复它。这很常见,尤其在Notepad++和IDEA这类工具中,撤销操作和取消撤销等动作便使用了备忘录模式。

备忘录模式的目的是用于保存对象的内部状态而不破坏其封装结构,并在以后阶段恢复其状态。执行的是类似还原现场的工作

2.3.7.1 UML类图

在这里插入图片描述

2.3.7.2 参与者

  1. Memento
    备忘录存储原发器对象的内部状态。

  2. Originator
    类似于客户端
    原发器创建一个备忘录,用以记录当前时刻它的内部状态。
    使用备忘录恢复内部状态.

  3. Caretaker
    负责保存好备忘录。
    不能对备忘录的内容进行操作或检查。

2.3.7.3 实践

package com.chapter3.memento;

public class CarOriginator {

    private String state;
    
    public void setState(String state) {
        this.state = state;
    }
    
    public String getState() {
        return this.state;
    }
    
    public Memento saveState() {
        return new Memento(this.state);
    }
 
    public void restoreState(Memento memento) {
        this.state = memento.getState();
    }
    
    /**
     * Memento class
     */
    public static class Memento {
        private final String state;

        public Memento(String state) {
            this.state = state;
        }

        private String getState() {
            return state;
        }
    }

}

state表示测试运行时汽车的参数,这是我们想要保存的对象的状态。

package com.chapter3.memento;

public class CarCaretaker {

   public static void main(String s[]) {
      new CarCaretaker().runMechanicTest();
   }

   public void runMechanicTest() {
      CarOriginator.Memento savedState = new CarOriginator.Memento("");
      CarOriginator originator = new CarOriginator();
      originator.setState("State1");
      originator.setState("State2");
      savedState = originator.saveState();
      originator.setState("State3");

      originator.restoreState(savedState);
      System.out.println("final state:" + originator.getState());
   }

}

2.3.7.4 适用情况

只要需要执行回滚操作,就会使用备忘录。

2.3.8 状态模式

状态模式知识面向对象设计中的有限状态机的实现。

状态模式定义:对象行为的变化是由于状态的变化引入,那么即当内部状态发生变化的时候,就会改变对象的行为,而这种改变视乎就改变了整个类。

2.3.8.1 UML

在这里插入图片描述

2.3.8.2 适用性

  1. 一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为。

  2. 一个操作中含有庞大的多分支的条件语句,且这些分支依赖于该对象的状态。
    这个状态通常用一个或多个枚举常量表示。
    通常,有多个操作包含这一相同的条件结构。
    State模式将每一个条件分支放入一个独立的类中。
    这使得你可以根据对象自身的情况将对象的状态作为一个对象,这一对象可以不依赖于其他对象而独立变化。

2.3.8.3 实践

Java重构-策略模式、状态模式、卫语句
阿里巴巴出品的Java开发手册提出,如果if-else分支过多,则可以使用卫语句、状态模式、策略模式替换。

2.3.8.4 参与者

  1. Context
    定义客户感兴趣的接口。
    维护一个ConcreteState子类的实例,这个实例定义当前状态。
  2. State
    定义一个接口以封装与Context的一个特定状态相关的行为。
  3. ConcreteStatesubclasses
    每一子类实现一个与Context的一个状态相关的行为。

2.3.9 策略模式

行为模式的一个特定情况,是我们需要改变解决一个问题与另外一个问题的方式。本质是分离算法,选择实现。策略模式对应于解决某一个问题的一个算法组,允许用户从该算法族中任意选择一个算法解决某一问题,同时可以方便的更换算法或增加新的算法,并由客户端决定调用哪个算法。
策略模式与状态模式非常相似,状态模式多用于有限状态机,状态之间的跃迁影响到行为,而策略则多对应于某一个问题的具体算法。以禅道Bug而言,对于一个Bug而言有三种状态,已激活,延迟处理和已关闭。

2.3.9.1 UML

策略模式的结构与状态模式相同。
在这里插入图片描述

2.3.9.2 参与者

  1. Strategy
    定义所有支持的算法的公共接口。Context使用这个接口来调用某ConcreteStrategy定义的算法。

  2. ConcreteStrategy
    以Strategy接口实现某具体算法。

  3. Context
    用一个ConcreteStrategy对象来配置。
    维护一个对Strategy对象的引用。
    可定义一个接口来让Stategy访问它的数据。

2.3.9.3 实践

策略模式+工厂模式 去除if-else
解锁新姿势:探讨复杂的 if-else 语句“优雅处理”的思路

2.3.10 模板方法模式

使用模板方法模式的目的是避免编写重复的代码,以便开发人员可以专注于核心逻辑。
模板方法模式现实的最好方式是抽象类。抽象类可以提供给我们所知道的实现区域,默认实现和为实现而保持开发的区域即为抽象。

定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。
TemplateMethod使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

2.3.10.1 参与者

  1. AbstractClass
    定义抽象的原语操作(primitiveoperation),具体的子类将重定义它们以实现一个算法的各步骤。
    实现一个模板方法,定义一个算法的骨架。
    该模板方法不仅调用原语操作,也调用定义在AbstractClass或其他对象中的操作。
  2. ConcreteClass
    实现原语操作以完成算法中与特定子类相关的步骤。

2.3.10.2 UML

在这里插入图片描述

2.3.10.3 实践

这个模板方法已经得到了充分的实践了。在智慧营区发过程中Task、EventResponse频繁使用的就是模板方法。maven的生命周期如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.3.11 空对象模式

模拟类具有相同的结构,但是什么也不做。

2.3.11.1 UML

在这里插入图片描述

2.3.11.2 实践

智慧营区中NullServiceEntity扮演了类似的结构。在JDK8引入的Optional中,有一个单例的成员empty也起到了类似的作用。

2.3.12 访问者模式

访问者模式的目的是将操作与其操作的对象结构分开,允许添加新操作而不更改结构类。

访问者模式在单个类中定义了一组操作:它为每个类型的对象定义一个方法,该方法来自它必须操作的结构。只需创建另一个访问者即可添加一组新操作。

2.3.12.1 UML

在这里插入图片描述

2.3.12.2 参与者

  1. Visitor
    为该对象结构中ConcreteElement的每一个类声明一个Visit操作。
    该操作的名字和特征标识了发送Visit请求给该访问者的那个类。
    这使得访问者可以确定正被访问元素的具体的类。
    这样访问者就可以通过该元素的特定接口直接访问它。
  2. ConcreteVisitor
    实现每个由Visitor声明的操作。
    每个操作实现本算法的一部分,而该算法片断乃是对应于结构中对象的类。
    ConcreteVisitor为该算法提供了上下文并存储它的局部状态。
    这一状态常常在遍历该结构的过程中累积结果。
  3. Element
    定义一个Accept操作,它以一个访问者为参数。
  4. ConcreteElement
    实现Accept操作,该操作以一个访问者为参数。
  5. ObjectStructure
    能枚举它的元素。
    可以提供一个高层的接口以允许该访问者访问它的元素。
    可以是一个复合或是一个集合,如一个列表或一个无序集合。

2.3.12.3 适用场景

命令模式与访问者有很大的相似性。
如果将一个抽象的save方法添加到基本形状类中,并且为每个形状扩展它,我们就解决了这个问题。这个解决方案是最直观的,但不是最好的。首先,每个类都应该只承担一项责任。汽车,如果需要更改我们想要保存每个形状的格式会发生什么?如果是想相同的方法来生成XML,那么是否必须更改为JSON格式?这种设计绝对不遵循开放/闭合原则。
因此可以把这一组操作聚拢成一个访问者,而操作的对象不需要发生变化,需要新的操作时,只需要添加一个新的访问者即可。

  1. 对象结构稳定,但经常要在此对象结构上定义新的操作。
  2. 需要对一个对象结构中的对象进行很多并且不相关的操作,而需要避免这些操作”污染这些对象的类”,也不希望在增加新操作时修改这些类。
    3 总结
    本章讨论了各种行为型模式,我也快累死了,眼睛疼。之前较为困惑的访问者模式、备忘录模式、中介者模式、状态和策略模式、命令和解释器模式也有了一定程度的理解。这些模式有助于我们以受控的方式来管理对象的行为。等下周工作,如果有时间,就可以解决掉Task和EventResponse中那个冗长的switch-case了,使用策略+工厂的实现。
    最后,也以p59页的摘抄结束这个博客,“无论你遇见谁,他都是你生命中该出现的人,也一定会教会你一些什么,永远不要去责怪你生命中的任何人。”
							2019-12-21 22:15周六于湖墅新村

4 引用

责任链实现
观察者模式迭代器的理解与实现

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章