【Java 行为型设计模式 I】模板方法模式、解释器模式详解

 
愿你如阳光,明媚不忧伤。

 


1. 行为型模式的特点和分类

Behavioral Pattern 用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。

  • 行为型模式总览
范围 \ 目的行为型模式 11
类模式 2模板方法、解释器
对象模式 9策略
命令
职责链
状态
观察者
中介者
迭代器
访问者
备忘录
  1. 模板方法(TemplateMethod)模式:定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。
  2. 解释器(Interpreter)模式:提供如何定义语言的文法,以及对语言句子的解释方法,即解释器。
  3. 策略(Strategy)模式:定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的改变不会影响使用算法的客户。
  4. 命令(Command)模式:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开
  5. 职责链(Chain of Responsibility)模式:把请求从链中的一个对象传到下一个对象,直到请求被响应为止。通过这种方式去除对象之间的耦合。
  6. 状态(State)模式:允许一个对象在其内部状态发生改变时改变其行为能力
  7. 观察者(Observer)模式:多个对象间存在一对多关系,当一个对象发生改变时,把这种改变通知给其他多个对象,从而影响其他对象的行为。
  8. 中介者(Mediator)模式:定义一个中介对象来简化原有对象之间的交互关系,降低系统中对象间的耦合度,使原有对象之间不必相互了解。
  9. 迭代器(Iterator)模式:提供一种方法来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。
  10. 访问者(Visitor)模式:在不改变集合元素的前提下,为一个集合中的每个元素提供多种访问方式,即每个元素有多个访问者对象访问。
  11. 备忘录(Memento)模式:在不破坏封装性的前提下,获取并保存一个对象的内部状态,以便以后恢复它。

 


2. 模板方法模式

Template Method 定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。例如去银行办理业务一般要经过以下4个流程:取号、排队、办理具体业务、对银行工作人员进行评分等,其中取号、排队和对银行工作人员进行评分的业务对每个客户是一样的,可以在父类中实现,但是办理具体业务却因人而异,它可能是存款、取款或者转账等,可以延迟到子类中实现。

模板方法模式优点和缺点
  • 优点
  1. 它封装了不变部分,扩展可变部分。它把认为是不变部分的算法封装到父类中实现,而把可变部分算法由子类继承实现,便于子类继续扩展。
  2. 它在父类中提取了公共的部分代码,便于代码复用。
  3. 部分方法是由子类实现的,因此子类可以通过扩展方式增加相应的功能,符合开闭原则。
  • 缺点
  1. 对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象,间接地增加了系统实现的复杂度。
  2. 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。
  3. 由于继承关系自身的缺点,如果父类添加新的抽象方法,则所有子类都要改一遍。
模板方法模式的应用场景
  1. 算法的整体步骤很固定,但其中个别部分易变时,这时候可以使用模板方法模式,将容易变的部分抽象出来,供子类实现。
  2. 当多个子类存在公共的行为时,可以将其提取出来并集中到一个公共父类中以避免代码重复。首先,要识别现有代码中的不同之处,并且将不同之处分离为新的操作。最后,用一个调用这些新的操作的模板方法来替换这些不同的代码。
  3. 当需要控制子类的扩展时,模板方法只在特定点调用钩子操作,这样就只允许在这些点进行扩展。
模板方法模式的结构
模板方法模式需要注意抽象类与具体子类之间的协作。它用到了虚函数的多态性技术以及“不用调用我,让我来调用你”的反向控制技术。
1. 抽象模板类(Abstract Class),负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成:
        I 模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法。
        II 基本方法:是整个算法中的一个步骤,包含以下几种类型:
                a 抽象方法:在抽象类中声明,由具体子类实现。
                b 具体方法:在抽象类中已经实现,在具体子类中可以继承或重写它。
                c 钩子方法:在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。
2. 具体实现类(Concrete Class):实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的一个组成步骤。
抽象模板类AbstractClass +templateMethod() : void +specificMethod() : void +abstractMethod1() : void +abstractMethod2() : void 具体实现类ConcreteClass +abstractMethod1() : void +abstractMethod2() : void 访问类Client +main() : void Inheritance
模板方法模式的实现
package com.example.demo.controller;

public class TemplateMethodController {
    public static void main(String[] args) {
        AbstractClass ac = new ConcreteClass();
        ac.templateMethod();
    }
}

// 抽象模板类
abstract class AbstractClass {
    // 模板方法
    public void templateMethod() {
        specificMethod();
        abstractMethod1();
        abstractMethod2();
    }

    // 具体方法
    public void specificMethod() {
        System.out.println("抽象模板类中的具体方法被调用");
    }

    // 抽象方法1
    public abstract void abstractMethod1();

    // 抽象方法2
    public abstract void abstractMethod2();
}

// 具体子类
class ConcreteClass extends AbstractClass {

    @Override
    public void abstractMethod1() {
        System.out.println("抽象模板类中抽象方法1的实现被调用");
    }

    @Override
    public void abstractMethod2() {
        System.out.println("抽象模板类中抽象方法2的实现被调用");
    }
}

-----------------------------------------------------------------
・【运行结果】
	抽象模板类中的具体方法被调用
	抽象模板类中抽象方法1的实现被调用
	抽象模板类中抽象方法2的实现被调用

模板方法模式的扩展

在模板方法模式中,基本方法包含:抽象方法、具体方法和钩子方法,正确使用“钩子方法”可以使得子类控制父类的行为。

  • 钩子模板方法
抽象模板类HookAbstractClass +templateMethod() : void +specificMethod() : void +HookMethod1() : void +HookMethod2() : void +abstractMethod1() : void +abstractMethod2() : void 具体实现类ConcreteClass +HookMethod1() : void +HookMethod2() : void +abstractMethod1() : void +abstractMethod2() : void 访问类Client -hac:HookAbstractClass +main() : void Inheritance
package com.example.demo.controller;

public class HookTemplateMethodController {
    public static void main(String[] args) {
        HookAbstractClass hac = new HookConcreteClass();
        hac.TemplateMethod();
    }
}

// 含钩子方法的抽象类
abstract class HookAbstractClass {
    // 模板方法
    public void TemplateMethod() {
        abstractMethod1();
        // 需要子类重写的空方法
        HookMethod1();
        // 判断的逻辑方法
        if (HookMethod2()) {
            SpecificMethod();
        }
        abstractMethod2();
    }

    // 具体方法
    public void SpecificMethod() {
        System.out.println("抽象模板类中的具体方法被调用...");
    }

    // 钩子方法1
    public void HookMethod1() {
    }

    // 钩子方法2
    public boolean HookMethod2() {
        return true;
    }

    // 抽象方法1
    public abstract void abstractMethod1();

    // 抽象方法2
    public abstract void abstractMethod2();
}

// 含钩子方法的具体子类
class HookConcreteClass extends HookAbstractClass {
    public void abstractMethod1() {
        System.out.println("抽象模板中方法1的实现被调用...");
    }

    public void abstractMethod2() {
        System.out.println("抽象模板中方法2的实现被调用...");
    }

    public void HookMethod1() {
        System.out.println("钩子方法1被重写...");
    }

    public boolean HookMethod2() {
        return false;
    }
}
-----------------------------------------------------------------
・【运行结果】
	抽象模板中方法1的实现被调用...
	钩子方法1被重写...
	抽象模板中方法2的实现被调用...
	

 


3. 解释器方法模式

Interpreter 给分析对象定义一个语言,并定义该语言的文法表示,再设计一个解析器来解释语言中的句子。也就是说,用编译语言的方式来分析应用中的实例。这种模式实现了文法表达式处理的接口,该接口解释一个特定的上下文。这里提到的文法和句子的概念同编译原理中的描述相同,“文法”指语言的语法规则,而“句子”是语言集中的元素。例如,汉语中的句子有很多,“我是中国人”是其中的一个句子,可以用一棵语法树来直观地描述语言中的句子。

解释器方法模式优点和缺点
  • 优点
  1. 扩展性好。由于在解释器模式中使用类来表示语言的文法规则,因此可以通过继承等机制来改变或扩展文法。
  2. 容易实现。在语法树中的每个表达式节点类都是相似的,所以实现其文法较为容易。
  • 缺点
  1. 执行效率较低。解释器模式中通常使用大量的循环和递归调用,当要解释的句子较复杂时,其运行速度很慢,且代码的调试过程也比较麻烦。
  2. 会引起类膨胀。解释器模式中的每条规则至少需要定义一个类,当包含的文法规则很多时,类的个数将急剧增加,导致系统难以管理与维护。
  3. 可应用的场景比较少。在软件开发中,需要定义语言文法的应用实例非常少,所以这种模式很少被使用到。
解释器方法模式的应用场景
  1. 当语言的文法较为简单,且执行效率不是关键问题时。
  2. 当问题重复出现,且可以用一种简单的语言来进行表达时。
  3. 当一个语言需要解释执行,并且语言中的句子可以表示为一个抽象语法树的时候,如 XML 文档解释。
解释器方法模式的结构
解释器模式常用于对简单语言的编译或分析实例中,为了掌握好它的结构与实现,必须先了解编译原理中的“文法、句子、语法树”等相关概念。
文法是用于描述语言的语法结构的形式规则。句子是语言的基本单位,是语言集中的一个元素,它由终结符构成,能由“文法”推导出。语法树是句子结构的一种树型表示,它代表了句子的推导结果,它有利于理解句子语法结构的层次。
1. 抽象表达式(Abstract Expression)角色:定义解释器的接口,约定解释器的解释操作,主要包含解释方法 interpret()。
2. 终结符表达式(Terminal Expression)角色:是抽象表达式的子类,用来实现文法中与终结符相关的操作,文法中的每一个终结符都有一个具体终结表达式与之相对应。
3. 非终结符表达式(Nonterminal Expression)角色:也是抽象表达式的子类,用来实现文法中与非终结符相关的操作,文法中的每条规则都对应于一个非终结符表达式。
4. 环境(Context)角色:通常包含各个解释器需要的数据或是公共的功能,一般用来传递被所有解释器共享的数据,后面的解释器可以从这里获取这些值。
5. 客户端(Client):主要任务是将需要分析的句子或表达式转换成使用解释器对象描述的抽象语法树,然后调用解释器的解释方法,当然也可以通过环境角色间接访问解释器的解释方法。
抽象表达式AbstractExpression +interpret(String info) : Object 终结符表达式TerminalExpression +interpret(String info) : Object 非终结符表达式NonterminalExpression -exp1:AbstractExpression -exp2:AbstractExpression +interpret(String info) : Object 环境角色Context -exp:AbstractExpression +Context() +operation(Stirng info) : void 访问类Client +main() : void Realization Realization Aggregation Aggregation
解释器方法模式的实现
package com.example.demo.controller;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

public class InterpreterController {
    /*文法规则
      <expression> ::= <city>的<person>
      <city> ::= 韶关|广州
      <person> ::= 老人|妇女|儿童
    */
    public static void main(String[] args) {
        Context bus = new Context();
        bus.freeRide("韶关的老人");
        bus.freeRide("韶关的年轻人");
        bus.freeRide("广州的妇女");
        bus.freeRide("广州的儿童");
        bus.freeRide("山东的儿童");
    }
}

// 抽象表达式类
interface Expression {
    boolean interpret(String info);
}

// 终结符表达式类
class TerminalExpression implements Expression {
    private final Set<String> set = new HashSet<>();

    public TerminalExpression(String[] data) {
        Collections.addAll(set, data);
    }

    public boolean interpret(String info) {
        return set.contains(info);
    }
}

//非终结符表达式类
class NonterminalExpression implements Expression {
    private final Expression city;
    private final Expression person;

    public NonterminalExpression(Expression city, Expression person) {
        this.city = city;
        this.person = person;
    }

    public boolean interpret(String info) {
        String[] s = info.split("的");
        return city.interpret(s[0]) && person.interpret(s[1]);
    }
}

//环境类
class Context {
    private final Expression cityPerson;

    public Context() {
        String[] cities = {"韶关", "广州"};
        Expression city = new TerminalExpression(cities);
        String[] persons = {"老人", "妇女", "儿童"};
        Expression person = new TerminalExpression(persons);
        cityPerson = new NonterminalExpression(city, person);
    }

    public void freeRide(String info) {
        boolean ok = cityPerson.interpret(info);
        if (ok) System.out.println("您是" + info + ",您本次乘车免费!");
        else System.out.println(info + ",您不是免费人员,本次乘车扣费2元!");
    }
}

-----------------------------------------------------------------
・【运行结果】
	您是韶关的老人,您本次乘车免费!
	韶关的年轻人,您不是免费人员,本次乘车扣费2元!
	您是广州的妇女,您本次乘车免费!
	您是广州的儿童,您本次乘车免费!
	山东的儿童,您不是免费人员,本次乘车扣费2元!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值