软件开发设计模式之三(行为型 )

通过行为型模式,可以更加清晰地划分类与对象的职责,并研究系统在运行时实例对象 之间的交互。在系统运行时,对象并不是孤立的,它们可以通过相互通信与协作完成某些复杂功能,一个对象在运行时也将影响到其他对象的运行。行为型模式共十一种:策略模式、模板方法模式、观察者模式、迭代器模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。


策略模式(strategy)

定义:定义一组算法,将每个算法都封装起来,并且使他们之间可以互换。
使用场景:

  • 针对同类问题的多种处理方式,仅仅是 具体行为 有差别。
  • 需要安全地封装多种 同类型 的操作。
  • 出现同一抽象类,有多个子类,而又需使用 if-else 或 switch-case 来选择具体子类。

策略模式的结构

  • 封装类:也叫上下文,对策略进行二次封装,目的是避免高层模块对策略的直接调用。
  • 抽象策略:通常情况下为一个接口,当各个实现类中存在着重复的逻辑时,则使用抽象类来封装这部分公共的代码,此时,策略模式看上去更像是模版方法模式。
  • 具体策略:具体策略角色通常由一组封装了算法的类来担任,这些类之间可以根据需要自由替换。

在软件开发中也常常遇到类似的情况,实现某一个功能有多个途径,此时可以使用一种设计模式来使得系统可以灵活地选择解决途径,也能够方便地增加新的解决途径。完成一项任务,往往可以有多种不同的方式,每一种方式称为一个策略,我们可以根据环境或者条件的不同选择不同的策略来完成该项任务。

public interface CalculateStragety {
    /**
     * 按距离来计算价格
     * @param km 公里
     * @return 返回价格
     */
    int calculatePrice();
}

public class BusStragety implements CalculateStragety {
    @Override
    public int calculatePrice(int km) {
        // 公交车价格计算策略
    }
}

public class SubwayStragety implements CalculateStragety {
    @Override
    public int calculatePrice(int km) {
        // 地铁价格计算策略
    }
}

// 客户端实现: 出行价格计算器
public class TranficCalculator {
    CalculateStrategy mStrategy = null;

    public static void main(String[] args) {
        TranficCalculator calculator = new TranficCalculator();
        // 设置计算策略
        calculator.setStrategy( new BusStrategy() );
        // 计算价格
        System.out.println("公交车乘16公里的价格: " + calculator.calculatePrice(16));
    }

    public void setStrategy(CalculateStrategy mStrategy) {
        this.mStrategy = mStrategy;
    }

    public int calculatePrice(int km) {
        return mStrategy.calculatePrice(km);
    }
}

策略模式是一种简单常用的模式,我们在进行开发的时候,会经常有意无意地使用它。做面向对象设计的,对策略模式一定很熟悉,因为它实质上就是面向对象中的继承和多态,在看完策略模式的通用代码后,我想,即使之前从来没有听说过策略模式,在开发过程中也一定使用过它吧?至少在在以下两种情况下,大家可以考虑使用策略模式,

  • 几个类的主要逻辑相同,只在部分逻辑的算法和行为上稍有区别的情况。
  • 有几种相似的行为,或者说算法,客户端需要动态地决定使用哪一种,那么可以使用策略模式,将这些算法封装起来供客户端调用。

策略模式的优缺点

  • 优点:很好地演示了开闭原则,也就定义了抽象;耦合度相对较低,扩展方法。
  • 缺点:随着策略的增加,子类会变得繁多。

模版方法模式(Template Method)

定义:定义一个操作中算法的框架,而将一些步骤延迟到子类中,使得子类可以不改变算法的结构即可重定义该算法中的某些特定步骤。说的通俗一点,就是为子类设计一个模板以便于子类复用里面的方法。为了避免子类恶意修改方法的实现细节,一般模板方法模式都会在方法上加final。

应用场景:

  • 将算法不变的部分一次实现,可变部分由子类实现
  • 控制子类扩展,实现hook方法
public abstract class AbstractClass {
    protected abstract void doAnything();
    protected abstract void doSomething();
    protected boolean isDoSomething(){ //父类方法返回真
        return true;
    }
    public final void templateMethod(){
        /*
         * 调用基本方法,完成相关的逻辑
         */
        this.doAnything();
        if(this.isDoSomething())
            this.doSomething();
    }
}

public class ConcreteClass1 extends AbstractClass {
    private boolean isDoSth;
    @Override
    protected void doAnything() {
        // TODO Auto-generated method stub
        //子类实现具体
    }

    @Override
    protected void doSomething() {
        // TODO Auto-generated method stub    
    }
    protected void setDo(boolean isDo){
        this.isDoSth = isDo;
    }
    protected boolean isDoSomething(){
        return isDoSth;
    }
}

模板方法模式是通过父类建立框架,子类在重写了父类部分方法之后,在调用从父类继承的方法,产生不同的效果,通过修改子类,影响父类行为的结果,模板方法在一些开源框架中应用非常多,它提供了一个抽象类,然后开源框架写了一堆子类,如果需要扩展功能,可以继承此抽象类,然后覆写protected基本方法,然后在调用一个类似TemplateMethod()的模板方法,完成扩展开发。

模板方法模式的优缺点:

  • 优点:封装不变部分,扩展可变部分。把认为不变部分的算法封装到父类中实现,而可变部分的则可以通过继承来继续扩展;提取公共部分代码,便于维护;行为由父类控制,子类实现。
  • 缺点:按照设计习惯,抽象类负责声明最抽象、最一般的事物属性和方法,实现类负责完成具体的事务属性和方法,但是模板方式正好相反,子类执行的结果影响了父类的结果,会增加代码阅读的难度。

观察者模式(Observer Pattern)

定义:对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。观察者模式又叫做发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。

建立一种对象与对象之间的依赖关系,一个对象发生改变时将自动通知其他对象,其他对象将相应做出反应。在此,发生改变的对象称为观察目标,而被通知的对象称为观察者,一个观察目标可以对应多个观察者,而且这些观察者之间没有相互联系,可以根据需要增加和删除观察者,使得系统更易于扩展。

应用场景:

  • 当一个对象改变时,需要改变其他对象,但不了解其他对象的数量
  • 当一个对象应当负责通知其他对象的职责实现时,降低通知对象和被通知对象之间的耦合

主题角色:

public interface Subject
{
    public void registerObserver(Observer o);
    public void removeObserver(Observer o);
    public void notifyObservers();
}

public class ConcreteSubject implements Subject
{
    List<Observer> observers = new ArrayList<Observer>();

    @Override
    public void registerObserver(Observer o)
    {
        if (o == null)
            throw new NullPointerException();
        if (!observers.contains(o))
        {
            observers.add(o);
        }
    }

    @Override
    public void removeObserver(Observer o)
    {
        observers.remove(o);
    }

    @Override
    public void notifyObservers()
    {
        for (Observer o : observers)
            o.update();
    }
}

观察者角色:

public interface Observer
{
    public void update();
}

public class ConcreteObserver implements Observer
{
    @Override
    public void update()
    {
      //加入hashCode以区别不同的对象
      System.out.println(this.hashCode()+"  says: I'm notified !");
    }
}
public class Client
{
    public static void main(String[] args)
    {
        Subject subject = new ConcreteSubject();

        Observer o1 = new ConcreteObserver();
        Observer o2 = new ConcreteObserver();
        Observer o3 = new ConcreteObserver();

        subject.registerObserver(o1);
        subject.registerObserver(o2);
        subject.registerObserver(o3);
        subject.registerObserver(o1);//测试重复注册

        subject.notifyObservers();
    }
}

在观察者模式中,改变主题或观察者其中一方,并不会影响另一方,因为两者是松耦合的,所以只要他们之间的接口仍被遵守,我们就可以自由地改变他们。松耦合的设计之所以能让我们建立有弹性的系统,能够应付变化,是因为对象之间的互相依赖降到了最低。

观察者模式的优缺点:

  • 优点:观察者和被观察者是抽象耦合的;建立一套触发机制。。
  • 缺点:如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间;如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃;观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。

迭代器模式(Iterator)

定义:提供一种方法顺序访问一个聚合对象中的各种元素,而又不暴露该对象的内部表示。顾名思义,迭代器模式就是顺序访问聚集中的对象,一般来说,集合中非常常见,如果对集合类比较熟悉的话,理解本模式会十分轻松。这句话包含两层意思:一是需要遍历的对象,即聚集对象;二是迭代器对象,用于对聚集对象进行遍历访问。

应用场景:

  • 不暴露聚合体内部表示,访问该对象的内容
  • 为聚合体对象提供多种遍历方式
  • 为不同的聚合结构提供统一的访问接口
**
 * 迭代器接口
 */
public interface Iterator<T> {
    /**
     * 是否还有下一个元素
     * @return  true 表示有,false 表示没有
     * */
    boolean hasNext();

    /**
     * 返回当前位置的元素并将位置移至下一位
     * */
     T next();
}

/**
 * 具体迭代器
 */
public class ConcreteIterator<T> implements Iterator{
    private List<T> list = new ArrayList<>();
    private int cursor = 0;

    @Override
    public boolean hasNext() {
        return cursor != list.size();
    }

    @Override
    public Object next() {
        T obj = null;
        if(this.hasNext()){
            obj = this.list.get(cursor++);
        }
        return obj;
    }
}


/**
 * 容器接口
 */
public interface Aggregate<T> {
    /**
     * 添加一个元素
     * @param obj 元素对象
     * */
    void add(T obj);

    /**
     * 移除一个元素
     * @param obj 元素对象
     * */
    void remove(T obj);

    /**
     * 获取容器的迭代器
     * @return 迭代器对象
     * */
    Iterator<T>  iterator();
}

/**
 * 具体容器接口
 */
public class ConcreteAggregate<T> implements Aggregate<T>{
    private List<T> list = new ArrayList<>();

    @Override
    public void add(T obj) {
        list.add(obj);
    }

    @Override
    public void remove(T obj) {
        list.remove(obj);
    }

    @Override
    public Iterator<T> iterator() {
        return new ConcreteIterator<>();
    }
}

//client
public class Client {
    public static void main(String[] args){
        Aggregate<String> a = new ConcreteAggregate<>();
        a.add("Android");
        a.add("Studio\n");
        a.add("SM");
        a.add("Brother");
        Iterator<String> i = a.iterator();
        while(i.hasNext()){
            System.out.println(i.next());
        }
    }
}

总的来说: 迭代器模式是与集合共生共死的,一般来说,我们只要实现一个集合,就需要同时提供这个集合的迭代器,就像java中的Collection,List、Set、Map等,这些集合都有自己的迭代器。假如我们要实现一个这样的新的容器,当然也需要引入迭代器模式,给我们的容器实现一个迭代器。

Android自身源码中也为我们提供了迭代器遍历数据,最为典型的例子就是数据库查询使用Cursor(当我们使用SQLiteDatabase的query方法查询数据库时返回的),该游标对象实质就是一个具体的迭代器,我们可以使用它来遍历数据库查询所得的结果集。

迭代器模式的优缺点:

  • 优点:支持以不同的方式遍容器对象,也可以有多个遍历,弱化了容器类与遍历算法之间的关系。
  • 缺点:类文件的增加。

责任链模式(Chain of Responsibility)

定义:使多个对象都有机会处理请求,从而避免了请求的发送者和接受者之间的耦合关系。将这些对象连成链,并沿着这条链传递该请求,直至有对象处理它为止。通俗讲每个节点看作一对象,每一对象拥有不同的处理逻辑,将一请求从链式的首端发出,沿着链的路径一次传递每个节点对象,直至有对象处理这个请求为止。

应用场景:

  • 将请求发送给多个对象中的一个,但不显式指定接收者
  • 多个对象可以处理请求,但预先不知道哪个对象处理哪个请求
  • 处理请求的对象应该被动态设定

例如小民是请求的发起者,而老板则是处于链条顶端的类,小民从链的底端开始发出一个申请报账的请求,首先由组长处理该请求,组长比对后发现自己权限不够于是将该请求转发给位于链中下一个节点的主管,主管比对后发现自己权限不足又将该请求转发给经理,经理也基于同样的原因将请求转发给老板,这样层层转达直至请求被处理。既至始至终小民关心的是报账结果,而不用在乎处理者是谁。责任链模式在这里很好地将请求的发起者与处理者解耦。

// 抽象领导者
public abstract class Leader {
    protected Leader nextHandler; // 上一级领导处理者

    /**
     * 处理报账请求
     * @param money 能批复的报账额度
     */
    public final void handleRequest() {
        if( money < limit() ) {
            handle(money);
        } else {
            if( null != nextHandlder ) {
                nextHandler.handleRequest(money);
            }
        }
    }

    /**
     * 自身能批复的额度权限
     * @return 额度
     */
    public abstract int limit();

    /**
     * 处理报账行为
     * @param money 具体金额
     */
    pulbic abstract void handle(int money);
}

public class GroupLeader extends Leader {
    @Override
    public int limit() {
        return 1000;
    }
    @Override
    public void handle(int money) {
        Ststen.out.println("组长批复报销" + money + "元");
    }
}

public class Director extends Leader {
    @Override
    public int limit() {
        return 5000;
    }
    @Override
    public void handle(int money) {
        Ststen.out.println("主管批复报销" + money + "元");
    }
}

public class Manager extends Leader {
    @Override
    public int limit() {
        return 10000;
    }
    @Override
    public void handle(int money) {
        Ststen.out.println("经理批复报销" + money + "元");
    }
}

public class Boss extends Leader {
    @Override
    public int limit() {
        return Integer.MAX_VALUE;
    }
    @Override
    public void handle(int money) {
        Ststen.out.println("老板批复报销" + money + "元");
    }
}

// 小民从组长开始发起请求申请报账
public class XiaoMin {
    public static void main(String[] args) {
        // 构造各个领导对象
        GroupLeader groupLeader = new GroupLeader();
        Director director = new Director();
        Manager manager = new Manager();
        Boss boss = new Boss();

        // 设置上一级领导处理者对象
        groupLeader.nextHandler = director;
        director.nextHandler = manager;
        manager.nextHandler = boss;

        // 发起报账申请
        groupLeader.handleRequest(50000);
    }
}

Android 中我们可以借鉴责任链模式的思想来优化 BroadcastReceiver 使之成为一个全局的责任链处理者。我们知道 Broadcast 可以被分为两种:

  • Normal Broadcast:普通广播,异步广播,发出时可被所有的接收者收到。
  • Ordered Broadcast:有序广播,依优先级依次传播的,直到有接收者将其终止或所有接收者都不终止它。

有序广播这一特性与我们的责任链模式很相近,通过它可实现一种全局的责任链事件处理。

责任链模式的优缺点:

  • 优点:对请求者和处理者关系解耦,提高代码灵活性。
  • 缺点:递归调用。特别是处理者太多,那么遍历定会影响性能。

命令模式(Command Pattern)

定义:将一个请求封装为一个对象,从而使我们可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。命令模式是一种对象行为型模式,其别名为动作(Action)模式或事务(Transaction)模式。

在软件设计中,我们经常需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是哪个,我们只需在程序运行时指定具体的请求接收者即可,此时,可以使用命令模式来进行设计,使得请求发送者与请求接收者消除彼此之间的耦合,让对象之间的调用关系更加灵活。命令模式可以对发送者和接收者完全解耦,发送者与接收者之间没有直接引用关系,发送请求的对象只需要知道如何发送请求,而不必知道如何完成请求。

应用场景:

  • 业务请求保存在队列中,以备执行
  • 支持业务请求的redo/undo
  • 实现业务行为的参数化
// 接收者类
public class Receiver {
    /**
     * 真正执行具体命令逻辑的方法
     */
    public void action() {
        System.out.println("执行具体操作");
    }
}

// 抽象命令接口
public interface Command {
    /**
     * 执行具体操作的命令
     */
    void execute();
}

// 具体命令类
public class ConcreteCommand implements Command {
    private Receiver receiver; // 持有一个对接受者对象的引用
    public ConcreteCommand(Receiver receiver) {
        this.receiver = receiver;
    }
    @Override
    public void execute() {
        // 调用接收者的相关方法来执行具体逻辑
        receiver.action();
    }
}

// 请求者类
public class Invoker {
    private Command command; // 持有一个对应命令对象的引用

    public Invoker() {
        this.command = command;
    }

    public void action() {
        // 调用具体命令对象的相关方法,执行具体命令
        command.execute();
    }
}

// 客户端实现
public class Client {
    public static void main(String[] args) {
        // 构造一个接受者对象
        Receiver receiver = new Receiver();
        // 根据接收者对象构造一个命令对象
        Command command = new ConcreteCommand(receiver);
        // 根据具体的对象构造请求者对象
        Invoker invoker = new Invoker(command);
        // 执行请求方法
        invoker.action();
    }
}

命令模式的优缺点:

  • 优点:更灵活的控制性以及更好的扩展性;更弱的耦合性。
  • 缺点:类的膨胀,大量衍生类的创建。

备忘录模式(Memento)

定义:在不破坏封闭的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样以后就可将该对象恢复到原生保存的状态。主要目的是保存一个对象的某个状态,以便在适当的时候恢复对象,个人觉得叫备份模式更形象些,通俗的讲下:假设有原始类A,A中有各种属性,A可以决定需要备份的属性,备忘录类B是用来存储A的一些内部状态,类C呢,就是一个用来存储备忘录的,且只能存储,不能修改等操作。

应用场景:不破坏对象的封装,捕捉对象的内部状态,当需要时,可用于之后该对象的状态恢复

// “使命召唤”游戏 ( 简化的数据模型,仅供简单演示 )
public class CallOfDuty {
    private int mCheckPoint = 1;
    private int mLifeValue = 100;
    private String mWeapon = "沙漠之鹰";

    public void play() { // 玩游戏
        // 忽略实现细节
    }

    public void quit() { // 退出游戏
        // 忽略实现细节
    }

    public Memoto createMemoto() { // 创建备忘录
        Memoto memoto = new Memoto();
        memoto.mCheckPoint = mCheckPoint;
        memoto.mLifeValue = mLifeValue;
        memoto.mWeapon = mWeapon;
        return memoto;
    }

    public void restore(Memoto memoto) { // 恢复游戏
        this.mCheckPoint = memoto.mCheckPoint;
        this.mLifeValue = memoto.mLifeValue;
        this.mWeapon = memoto.mWeapon;
        System.out.println("恢复后的游戏属性: " + this.toString());
    }

    @Overrride
    public String toString() {
        return "CallOfDuty[mCheckPoint=..., mLifeValue=...]"
    }
}

// 备忘录类
public class Memoto {
    public int mCheckPoint;
    public int mLifeValue;
    public String mWeapon;
}

// Caretaker,负责管理 Memoto
public class Caretaker {
    Memoto memoto = null;

    public void archive(Memoto memoto) { // 存档
        this.memoto = memoto;
    }

    public Memoto getMemoto() { // 读取存档
        return memoto;
    }
}

// 客户端实现
public class Client {
    public static void main(String[] args) {
        CallOfDuty game = new CallOfDuty();

        // Step.01 游戏开始
        game.play();

        // Step.02 游戏存档
        Caretaker caretaker = new Caretaker();
        caretaker.archive( game.createMemoto() );

        // Step.03 退出游戏
        game.quit();

        // Step.04 恢复游戏
        CallOfDuty newGame = new CallOfDuty();
        newGame.restore( caretaker.getMemoto() );
    }
}

备忘录模式是在不破坏封装的条件下,通过备忘录对象 (Memoto) 存储另外一个对象内部状态的快照,在需求的时候把对象还原到存储的状态。

备忘录的优缺点:

  • 优点:恢复状态机制;信息封装
  • 缺点:消耗内存

状态模式(State Pattern)

定义:允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。

在很多情况下,一个对象的行为取决于一个或多个动态变化的属性,这样的属性叫做状态,这样的对象叫做有状态的(stateful)对象,这样的对象状态是从事先定义好的一系列值中取出的。当一个这样的对象与外部事件产生互动时,其内部状态就会改变,从而使得系统的行为也随之发生变化。

应用场景:

  • 一个对象的行为取决于它的状态
  • 因对象状态,该对象的操作含大量的条件分支语句,状态可以由枚举常量代替实现
/**
 * 抽象状态类
 */
public interface IWaterState {

    void printState();
}

/**
 * 冰水
 */
public class IceWaterState implements IWaterState {

    @Override
    public void printState() {
        System.out.println("Now state: Ice Water");
    }
}

/**
 * 温水
 */
public class WarmWaterState implements IWaterState {
    @Override
    public void printState() {
        System.out.println("Now state: Warm Water");
    }
}

/**
 * 沸水
 */
public class BoilingWaterState implements IWaterState {
    @Override
    public void printState() {
        System.out.println("Now state: Boiling Water");
    }
}

/**
 * 环境类
 */
public class WaterContext {
    private IWaterState mIWaterState;

    public IWaterState getIWaterState() {
        return mIWaterState;
    }

    public void setIWaterState(int i) {
        if (i == 0) {
            mIWaterState = new IceWaterState();
            return;
        }

        if (i == 1) {
            mIWaterState = new WarmWaterState();
            return;
        }

        if (i == 2) {
            mIWaterState = new BoilingWaterState();
            return;
        }
    }
}

public class MyClass {

    public static void main(String[] args) {
        IWaterState iWaterState;
        WaterContext waterContext = new WaterContext();
        //模拟状态改变
        for (int i = 0; i < 3; i++) {
            waterContext.setIWaterState(i);
            iWaterState = waterContext.getIWaterState();
            System.out.println("i=" + i);
            iWaterState.printState();
        }
    }
}

状态模式的优缺点:

  • 优点:将所有与一个特定的状态相关的行为都放入一个状态对象中,它提供了一个更好的方法来组织与特定状态相关的代码,将繁琐的状态判断转为结构清晰的状态类族。
  • 缺点:必然增加系统类和对象的个数。

访问者模式(Visitor)

定义:封装用于某种数据结构中各元素操作,且在不改数据结构的前提下定义这些元素的新操作。访问者模式把数据结构和作用于结构上的操作解耦合,使得操作集合可相对自由地演化。访问者模式适用于数据结构相对稳定算法又易变化的系统。因为访问者模式使得算法操作增加变得容易。若系统数据结构对象易于变化,经常有新的数据对象增加进来,则不适合使用访问者模式。访问者模式的优点是增加操作很容易,因为增加操作意味着增加新的访问者。访问者模式将有关行为集中到一个访问者对象中,其改变不影响系统数据结构。其缺点就是增加新的数据结构很困难。

应用场景:

  • 某聚合对象包含不同接口的元素类型,需要根据不同元素类型,对元素施加不同操作
  • 聚合对象结构极少变化,但常需要定义施加在聚合元素上的行为
/**
 * 抽象访问者角色:为每一个具体节点都准备了一个访问操作。
 * 这里由于有两个节点,因此,对应就有两个访问操作。
 */
public interface Visitor {
    /**
     * 对应于NodeA的访问操作
     */
    public void visit(NodeA node);

    /**
     * 对应于NodeB的访问操作
     */
    public void visit(NodeB node);
}

/**
 * 具体访问者VisitorA类
 */
public class VisitorA implements Visitor {
    /**
     * 对应于NodeA的访问操作
     */
    @Override
    public void visit(NodeA node) {
        System.out.println(node.operationA());
    }

    /**
     * 对应于NodeB的访问操作
     */
    @Override
    public void visit(NodeB node) {
        System.out.println(node.operationB());
    }

}

/**
 * 具体访问者VisitorB类
 */
public class VisitorB implements Visitor {
    /**
     * 对应于NodeA的访问操作
     */
    @Override
    public void visit(NodeA node) {
        System.out.println(node.operationA());
    }

    /**
     * 对应于NodeB的访问操作
     */
    @Override
    public void visit(NodeB node) {
        System.out.println(node.operationB());
    }

}

/**
 * 抽象节点类
 */
public abstract class Node {
    /**
     * 接受操作
     */
    public abstract void accept(Visitor visitor);
}

/**
 * 具体节点类NodeA
 */
public class NodeA extends Node {
    /**
     * 接受操作
     */
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }

    /**
     * NodeA特有的方法
     */
    public String operationA() {
        return "NodeA";
    }
}

/**
 * 具体节点类NodeB
 */
public class NodeB extends Node {
    /**
     * 接受方法
     */
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }

    /**
     * NodeB特有的方法
     */
    public String operationB() {
        return "NodeB";
    }
}

/**
 * 结构对象角色类
 * 这个结构对象角色持有一个聚集,并向外界提供add()方法作为对聚集的管理操作。通过调用这个方法,可以动态地增加一个新的节点。
 */
public class ObjectStructure {
    private List<Node> nodes = new ArrayList<Node>();

    /**
     * 执行方法操作
     */
    public void action(Visitor visitor) {
        for (Node node : nodes) {
            node.accept(visitor);
        }
    }

    /**
     * 添加一个新元素
     */
    public void add(Node node) {
        nodes.add(node);
    }
}

/**
 * 客户端类
 */
public class Client {
    public static void main(String[] args) {
        //创建一个结构对象
        ObjectStructure os = new ObjectStructure();
        //给结构增加一个节点
        os.add(new NodeA());
        //给结构增加一个节点
        os.add(new NodeB());
        //创建一个访问者
        Visitor visitor = new VisitorA();
        os.action(visitor);
    }
}

对象结构足够稳定,需在对象结构上经常定义新操作,且需对对象结构中的对象进行很多不同且不相关的操作,考虑访问者模式。

访问者模式的优缺点:

  • 优点:单一职责原则,即各角色职责分离;数据结构和作用于该结构上的操作解耦。
  • 缺点:具体元素对访问者公布细节;具体元素变更导致修改成本大;违反依赖倒置原则,即为了“区别对待”而依赖了具体类,没有依赖抽象。

中介者模式(Mediator Pattern)

中介者模式定义:用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。中介者模式又称为调停者模式,它是一种对象行为型模式。

应用场景:

  • 一组对象以有规律的方式交互,但交互方式复杂
  • 因某个对象引用并且和多个对象交互难以复用
  • 分布在不同类中的某个行为不使用多个子类进行定制
// 抽象中介者
public abstract class Mediator {
    protected ConcreteColleagueA colleagueA; // 具体同事类 A
    protected ConcreteColleagueB colleagueB; // 具体同事类 B

    // 抽象中介方法、子类实现
    public abstract void method();

    public void setColleagueA(ConcreteColleagueA colleagueA) {
        this.colleagueA = colleagueA;
    }

    public void setColleagueB(ConcreteColleagueB colleagueB) {
        this.colleagueB = colleagueB;
    }
}

// 具体中介者
public class ConcreteMediator extends Mediator {
    @Override
    public void method() {
        colleagueA.action();
        colleagueB.action();
    }
}

// 抽象同事
public abstract class Colleague {
    protected Mediator mediator; // 中介者对象

    public Colleague(Mediator mediator) {
        this.mediator = mediator;
    }

    // 同事角色的具体行为,由子类去实现
    public abstract void action();
}

// 具体同事 A
public class ConcreteColleagueA extends Colleague {
    public ConcreteColleagueA(Mediator mediator) {
        super(mediator);
    }

    @Override
    public void action() {
        System.out.println("Colleague A 将信息递交给中介者处理.");
    }
}

// 具体同事 B
public class ConcreteColleagueB extends Colleague {
    public ConcreteColleagueB(Mediator mediator) {
        super(mediator);
    }

    @Override
    public void action() {
        System.out.println("Colleague B 将信息递交给中介者处理.");
    }
}

协调多个交互的对象,Android 中这么多形形色色控件也算是交互对象。其中社交、网商等应用的用户登录模块,账号框、密码框、登录按钮之间的相互制约、联系,正是中介者模式的表现,具体的实例样式可自行尝试。

中介者模式的优缺点:

  • 优点:将网状般的依赖关系转化为以中介者为中心的星形结构,即使用中介者模式可对这种依赖关系进行解耦使逻辑结构清晰。
  • 缺点:若几个类间的依赖关系并不复杂,使用中介者模式反而会使原本不复杂的逻辑结构变得复杂。

解释器模式(Interpreter)

定义:给定一门语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子。解释器模式在项目中是比较少用到,没关系,为了全面掌握,也要学习下。

应用场景:

  • 当某个语言语法较为简单时
  • 解释语言的效率没有严格要求时
//抽象表达式
/*抽象表达式是生产语法集合(也叫语法树)的关键,每个语法集合完成指定语法解析任务,它是通过递归调用的方式,最终
由最小的语法单元进行解析完成*/
public abstract class Expression{
    //每个表达式必须有一个解析任务
    public abstract Object interpreter(Context context);
}

//终结符表达式  主要是处理场景元素和数据的转换 如:a+b+c中的"a""b""c"
public class TerminalExpression extends Expression{
    //通常终结符表达式只有一个,但是有多个对象
    public Object interpreter(Context context){
        return null;
    }
}

//非终结符表达式
/*
    每个非终结符表达式都代表一个文法规则,并且每个文法规则都只关心自己周边的文法规则结果(注意是结果),因此
    这就产生了每个终结符表达式调用自己周边的非终结符表达式,然后最终,最小的文法规则就是终结符表达式,终
    结符表达式的概念就是如此,不能在参与比自己更小的文法运算了
*/
public class NonterminalExpression extends Expression{
    //每个非终结符表达式都会对其他表达式产生依赖
    public NonterminalExpression(Expression... expression){
    }

    public Object interpreter(Context context){
        //进行文法处理
        return null;
    }
}

//场景类
public class Client{
    public static void main(String[] args){
        Context context=new Context();
        //通常一个语法容器,容纳一个具体的表达式,通常为ListArray,LinkedList,Stack等类型
        Stack<Expression> stack=null;
        for(;;){
            //进行语法判断,并产生递归调用
        }
        //产生一个完整的语法树,由各个具体的语法分析进行解析
        Expression expression=stack.pop();
        //具体元素进入场景
        expression.interpreter(context);
    }
}

解释模式的优缺点:

  • 优点:灵活的扩展性,既我们想对文法规则进行扩展延伸时,只需增加相应的非终结符解释器,并在构建抽象语法树时,使用到新增的解释器对象进行具体的解释即可。
  • 缺点:对于每一条文法对应至少一个解释器,其会生成大量的类,导致后期维护困难;构建其抽象语法树会显得异常繁琐,甚至可能出现需要构建多棵抽象语法树的情况。

总结

作为软件开发者,如果通晓了这23种设计模式,在工作中就可以得心应手,可以站在一个更高的层次去赏析程序代码、软件设计、架构,完成从代码工人到架构师的蜕变。只有在熟悉现有的设计模式基础上,总结出自已的模式,没有模式才是真正的高手。

来自网络图片:


参考书箱: