重学设计模式之行为模式

347 阅读27分钟

行为模式负责对象间的高效沟通和职责委派。

责任链模式

责任链模式允许你将请求沿着处理者链进行发送。收到请求后,每个处理者均可对请求进行处理,或将其传递给链上的下个处理者。

适用场景

  1. 需要使用不同方式处理不同种类请求,而且请求类型和顺序预先未知时,可以使用责任链模式。该模式能将多个处理者连接成一条链。接收到请求后,它会 “询问” 每个处理者是否能够对其进行处理。这样所有处理者都有机会来处理请求。
  2. 当必须按顺序执行多个处理者时,可以使用该模式。
  3. 如果所需处理者及其顺序必须在运行时进行改变,可以使用责任链模式。如果在处理者类中有对引用成员变量的设定方法,你将能动态地插入和移除处理者,或者改变其顺序。

实现方式

  1. 声明处理者接口,并描述处理方法。确定客户端如何将请求数据传递给方法。最灵活的方式是将请求转换为对象,然后将其以参数的形式传递给处理函数。
  2. 根据处理者接口创建抽象处理基类,消除重复代码。该类需要有一个成员变量来存储指向链上下个处理者的引用。
  3. 依次创建具体处理者子类并实现其处理方法。每个处理者在接收到请求后都必须做出两个决定:
    • 是否自行处理这个请求。
    • 是否将该请求沿着链进行传递。
  4. 自行组装链,或者从其他对象处获得预先组装好的链。在后一种情况下,必须实现工厂类以根据配置或环境设置来创建链。
  5. 客户端可以触发链中的任意处理者,而不仅仅是第一个。请求将通过链进行传递,直至某个处理者拒绝继续传递,或者请求到达链尾。
  6. 由于链的动态性,需要准备好处理以下情况:
    • 链中可能只有单个链接。
    • 部分请求可能无法到达链尾。
    • 其他请求可能直到链尾都未被处理。

代码实现

具体业务:员工报销费用申请需要领导审批,但是由于审批额度的问题需要相应等级的领导才能审批。

public abstract class Leader {


    // 上一级的领导者
    protected Leader nexHandler;

    public final void handleRequest(int money) {
        if (money < limit()) {
            handle(money);
        } else {
            if (null != nexHandler) {
                nexHandler.handleRequest(money);
            }
        }
    }

    /**
     * 当前级别领导能审批的报销额度
     *
     * @return 额度
     */
    public abstract int limit();


    /**
     * 处理额度审批
     *
     * @param money 具体金额
     */
    public abstract void handle(int money);
}

public class GroupLeader extends Leader{
    @Override
    public int limit() {
        return 1000;
    }

    @Override
    public void handle(int money) {
        System.out.println("小组长批复报销"+money+"元");
    }
}

public class Director extends Leader {
    @Override
    public int limit() {
        return 5000;
    }

    @Override
    public void handle(int money) {
        System.out.println("主管批复报销" + money + "元");
    }
}

public class Manager extends Leader {
    @Override
    public int limit() {
        return 10000;
    }

    @Override
    public void handle(int money) {
        System.out.println("经理批复报销" + money + "元");
    }
}

public class Boos extends Leader {
    @Override
    public int limit() {
        return Integer.MAX_VALUE;
    }

    @Override
    public void handle(int money) {
        System.out.println("劳保批复报销" + money + "元");
    }
}


public class DesignPatternsTest {

    @Test
    public void chainTest() {
        // 创建责任链中各个节点
        GroupLeader groupLeader = new GroupLeader();
        Director director = new Director();
        Manager manager = new Manager();
        Boos boos = new Boos();
        // 指定节点的下个节点
        groupLeader.nexHandler = director;
        director.nexHandler = manager;
        manager.nexHandler = boos;
        // 根据金额来确定处理节点
        groupLeader.handleRequest(300);
        groupLeader.handleRequest(4000);
        groupLeader.handleRequest(6000);
        groupLeader.handleRequest(100000);
    }
}

小组长批复报销300元
主管批复报销4000元
经理批复报销6000元
劳保批复报销100000

命令模式

命令模式它可将请求转换为一个包含与请求相关的所有信息的独立对象。该转换让你能根据不同的请求将方法参数化、延迟请求执行或将其放入队列中,且能实现可撤销操作。

适用场景

  1. 需要通过操作来参数化对象。 命令模式可将特定的方法调用转化为独立对象。这一改变也带来了许多有趣的应用:你可以将命令作为方法的参数进行传递、将命令保存在其他对象中,或者在运行时切换已连接的命令等。
  2. 需要要将操作放入队列中、操作的执行或者远程执行操作。
  3. 需要支持操作回滚。

实现方式

  1. 声明仅有一个执行方法的命令接口。
  2. 抽取请求并使之成为实现命令接口的具体命令类。每个类都必须有一组成员变量来保存请求参数和对于实际接收者对象的引用。所有这些变量的数值都必须通过命令构造函数进行初始化。
  3. 找到担任发送者职责的类。在这些类中添加保存命令的成员变量。发送者只能通过命令接口与其命令进行交互。发送者自身通常并不创建命令对象,而是通过客户端代码获取。
  4. 修改发送者使其执行命令,而非直接将请求发送给接收者。
  5. 客户端必须按照以下顺序来初始化对象:
    • 创建接收者。
    • 创建命令, 如有需要可将其关联至接收者。
    • 创建发送者并将其与特定命令关联。

代码实现

具体业务:模拟俄罗斯方块游戏简单操作

// 接收者角色 俄罗斯方块游戏机
public class TetrisMachine {

    /**
     * 真正处理"向左"操作的逻辑代码
     */
    public void toLeft() {
        System.out.println("向左");
    }

    /**
     * 真正处理"向右"操作的逻辑代码
     */
    public void toRight() {
        System.out.println("向右");
    }

    /**
     * 真正处理"加速向下"操作的逻辑代码
     */
    public void fastToBottom() {
        System.out.println("加速向下");
    }

    /**
     * 真正处理"变形"操作的逻辑代码
     */
    public void transform() {
        System.out.println("变形");
    }
}

// 命令抽象 定义执行接口
public interface Command {
    void execute();
}

public class LeftCommand implements Command {
    private TetrisMachine mMachine;

    public LeftCommand(TetrisMachine machine) {
        mMachine = machine;
    }

    @Override
    public void execute() {
        mMachine.toLeft();
    }
}

public class RightCommand implements Command {
    private TetrisMachine mMachine;

    public RightCommand(TetrisMachine machine) {
        mMachine = machine;
    }

    @Override
    public void execute() {
        mMachine.toRight();
    }
}

public class FallCommand implements Command {
    private TetrisMachine mMachine;

    public FallCommand(TetrisMachine machine) {
        mMachine = machine;
    }

    @Override
    public void execute() {
        mMachine.fastToBottom();
    }
}

public class TransformCommand implements Command {
    private TetrisMachine mMachine;

    public TransformCommand(TetrisMachine machine) {
        mMachine = machine;
    }

    @Override
    public void execute() {
        mMachine.transform();
    }
}

public class Buttons {

    private LeftCommand mLeftCommand;
    private RightCommand mRightCommand;
    private FallCommand mFallCommand;
    private TransformCommand mTransformCommand;

    /**
     * 设置向左移动命令对象
     *
     * @param leftCommand 向左移动的命令对象
     */
    public void setLeftCommand(LeftCommand leftCommand) {
        mLeftCommand = leftCommand;
    }

    /**
     * 设置右移动命令对象
     *
     * @param rightCommand 向右移动的命令对象
     */
    public void setRightCommand(RightCommand rightCommand) {
        mRightCommand = rightCommand;
    }

    /**
     * 设快速下落的命令对象
     *
     * @param fallCommand 快速下落移动的命令对象
     */
    public void setFallCommand(FallCommand fallCommand) {
        mFallCommand = fallCommand;
    }

    /**
     * 设变形的命令对象
     *
     * @param transformCommand 变形的命令对象
     */
    public void setTransformCommand(TransformCommand transformCommand) {
        mTransformCommand = transformCommand;
    }

    /**
     * 按下按钮向左
     */
    public void toLeft() {
        mLeftCommand.execute();
    }

    /**
     * 按下按钮向右
     */
    public void toRight() {
        mLeftCommand.execute();
    }

    /**
     * 按下按钮快速下落
     */
    public void fall() {
        mFallCommand.execute();
    }

    /**
     * 按下按钮改变形状
     */
    public void transform() {
        mTransformCommand.execute();
    }
}

public class DesignPatternsTest {

    @Test
    public void coomandTest() {
        // 命令接收者 创建俄罗斯方块游戏机
        TetrisMachine tetrisMachine = new TetrisMachine();
        // 构造四种命令
        LeftCommand leftCommand = new LeftCommand(tetrisMachine);
        RightCommand rightCommand = new RightCommand(tetrisMachine);
        FallCommand fallCommand = new FallCommand(tetrisMachine);
        TransformCommand transformCommand = new TransformCommand(tetrisMachine);
        // 按钮执行不同命令
        Buttons buttons = new Buttons();
        buttons.setLeftCommand(leftCommand);
        buttons.setRightCommand(rightCommand);
        buttons.setFallCommand(fallCommand);
        buttons.setTransformCommand(transformCommand);

        // 按下相应的键
        buttons.toLeft();
        buttons.toRight();
        buttons.transform();
        buttons.fall();
    }
}

向左
向左
变形
加速向下

迭代器模式

迭代器模式能在不暴露集合底层表现形式(列表、栈和树等)的情况下遍历集合中所有的元素。

适用场景

  1. 当集合背后为复杂的数据结构,且希望对客户端隐藏其复杂性时(出于使用便利性或安全性的考虑),可以使用迭代器模式。迭代器封装了与复杂数据结构进行交互的细节,为客户端提供多个访问集合元素的简单方法。 这种方式不仅对客户端来说非常方便,而且能避免客户端在直接与集合交互时执行错误或有害的操作,从而起到保护集合的作用。
  2. 使用该模式可以减少程序中重复的遍历代码。
  3. 如果希望代码能够遍历不同的甚至是无法预知的数据结构,可以使用迭代器模式。

实现方式

  1. 声明迭代器接口。该接口必须提供至少一个方法来获取集合中的下个元素。但为了使用方便,还可以添加一些其他方法,例如获取前一个元素、记录当前位置和判断迭代是否已结束。
  2. 声明集合接口并描述一个获取迭代器的方法。其返回值必须是迭代器接口。如果计划拥有多组不同的迭代器,则可以声明多个类似的方法。
  3. 为希望使用迭代器进行遍历的集合实现具体迭代器类。迭代器对象必须与单个集合实体链接,链接关系通常通过迭代器的构造函数建立。
  4. 在集合类中实现集合接口。其主要思想是针对特定集合为客户端代码提供创建迭代器的快捷方式。集合对象必须将自身传递给迭代器的构造函数来创建两者之间的链接。
  5. 检查客户端代码, 使用迭代器替代所有集合遍历代码。每当客户端需要遍历集合元素时都会获取一个新的迭代器。

代码实现

// 迭代器接口
public interface Iterator<T> {

    /**
     * 是否还有下一个元素
     * @return boolean
     */
    boolean haseNext();

    /**
     * 当前元素位置下一位元素
     * @return T
     */
    T next();
}

// 具体迭代器类
public class ConcreteIterator<T> implements Iterator<T> {

    private List<T> list = new ArrayList<>();

    private int cursor = 0;

    public ConcreteIterator(List<T> list) {
        this.list = list;
    }

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

    @Override
    public T next() {
        T obj = null;
        if (this.haseNext()) {
            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<>(list);
    }
}

public class DesignPatternsTest {

    @Test
    public void chainTest() {
        Aggregate<String> aggregate = new ConcreteAggregate<>();
        aggregate.add("刘备");
        aggregate.add("关羽");
        aggregate.add("张飞");
        aggregate.add("黄忠");
        Iterator<String> iterator = aggregate.iterator();
        while (iterator.haseNext()){
            System.out.println(iterator.next());
        }
    }
}

刘备
关羽
张飞
黄忠

中介者模式

中介者模式能减少对象之间混乱无序的依赖关系。该模式会限制对象之间的直接交互,迫使它们通过一个中介者对象进行合作。

适用场景

  1. 当一些对象和其他对象紧密耦合以致难以对其进行修改时,可使用中介者模式。该模式让你将对象间的所有关系抽取成为一个单独的类,以使对于特定组件的修改工作独立于其他组件。
  2. 当组件因过于依赖其他组件而无法在不同应用中复用时,可使用中介者模式。应用中介者模式后,每个组件不再知晓其他组件的情况。尽管这些组件无法直接交流,但它们仍可通过中介者对象进行间接交流。如果你希望在不同应用中复用一个组件,则需要为其提供一个新的中介者类。
  3. 如果为了能在不同情景下复用一些基本行为,导致你需要被迫创建大量组件子类时,可使用中介者模式。由于所有组件间关系都被包含在中介者中,因此无需修改组件就能方便地新建中介者类以定义新的组件合作方式。

实现方式

  1. 找到一组当前紧密耦合,且提供其独立性能带来更大好处的类(例如更易于维护或更方便复用)。
  2. 声明中介者接口并描述中介者和各种组件之间所需的交流接口。在绝大多数情况下,一个接收组件通知的方法就足够了。如果你希望在不同情景下复用组件类,那么该接口将非常重要。只要组件使用通用接口与其中介者合作,你就能将该组件与不同实现中的中介者进行连接。
  3. 实现具体中介者类。该类可从自行保存其下所有组件的引用中受益。
  4. 让中介者负责组件对象的创建和销毁。此后,中介者可能会与工厂或外观类似。
  5. 组件必须保存对于中介者对象的引用。该连接通常在组件的构造函数中建立,该函数会将中介者对象作为参数传递。
  6. 修改组件代码,使其可调用中介者的通知方法,而非其他组件的方法。然后将调用其他组件的代码抽取到中介者类中,并在中介者接收到该组件通知时执行这些代码。 ##代码实现 具体业务:计算机主板协调计算机哥哥成员。
// 抽象成员
public abstract class Colleague {
    // 每个成员都知道中介者
    protected Mediator mediator;

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

// 抽象中介者
public abstract class Mediator {

    /**
     * 成员对象改变通知中介者的方法
     * 在某一成员改变是由中介者去通知其它成员
     * @param colleague 成员
     */
    public abstract void changed(Colleague colleague);
}

// 成员零件Cpu
public class Cpu extends Colleague {
    // 音视频数据
    private String videoData, soundData;

    public Cpu(Mediator mediator) {
        super(mediator);
    }

    public String geVideoData() {
        return videoData;
    }

    public String getSoundData() {
        return soundData;
    }

    public void decodeData(String data) {
        // 分割音视频数据
        String[] temp = data.split(",");
        videoData = temp[0];
        soundData = temp[1];
        mediator.changed(this);
    }
}

// 成员零件光驱
public class CdDevice extends Colleague{
    // 视频数据
    private String data;

    public CdDevice(Mediator mediator) {
        super(mediator);
    }

    public String read(){
        return data;
    }

    public void load(){
        data = "视频数据,音频数据";
        // 通知中介者数据改变
        mediator.changed(this);
    }
}

// 成员零件显卡
public class GraphicsCard extends Colleague {

    public GraphicsCard(Mediator mediator) {
        super(mediator);
    }

    public void playVideo(String data) {
        System.out.println("视频:" + data);
    }
}

// 成员零件声卡
public class SoundCard extends Colleague {
    public SoundCard(Mediator mediator) {
        super(mediator);
    }

    public void playVoice(String data){
        System.out.println("音频:"+data);
    }
}

// 主板
public class MainBoard extends Mediator{

    private CdDevice mCdDevice;
    private Cpu mCpu;
    private SoundCard mSoundCard;
    private GraphicsCard mGraphicsCard;
    @Override
    public void changed(Colleague colleague) {
        if (colleague instanceof  CdDevice){
            handleCd((CdDevice) colleague);
        }
        if (colleague instanceof Cpu){
            handleCpu((Cpu) colleague);
        }
    }



    /**
     * 处理光驱读取数据后与其它设备交互
     * @param cdDevice 灌区设备
     */
    private void  handleCd(CdDevice cdDevice){
        mCpu.decodeData(cdDevice.read());
    }

    /**
     * 处理 CPU 读取数据后与其它设备交互
     * @param cpu cpu
     */
    private void handleCpu(Cpu cpu){
        mSoundCard.playVoice(cpu.getSoundData());
        mGraphicsCard.playVideo(cpu.geVideoData());
    }

    public void setCdDevice(CdDevice cdDevice) {
        mCdDevice = cdDevice;
    }

    public void setCpu(Cpu cpu) {
        mCpu = cpu;
    }

    public void setSoundCard(SoundCard soundCard) {
        mSoundCard = soundCard;
    }

    public void setGraphicsCard(GraphicsCard graphicsCard) {
        mGraphicsCard = graphicsCard;
    }
}

public class DesignPatternsTest {

    @Test
    public void mediatorTest() {
        // 构造中介者主板
        MainBoard mainBoard = new MainBoard();
        // 构造各个零件
        Cpu cpu = new Cpu(mainBoard);
        CdDevice cdDevice = new CdDevice(mainBoard);
        GraphicsCard graphicsCard = new GraphicsCard(mainBoard);
        SoundCard soundCard = new SoundCard(mainBoard);
        // 将各个零件安装到主板
        mainBoard.setCdDevice(cdDevice);
        mainBoard.setCpu(cpu);
        mainBoard.setGraphicsCard(graphicsCard);
        mainBoard.setSoundCard(soundCard);
        // 准备完成后播放视频
        cdDevice.load();
    }
}

音频:音频数据
视频:视频数据

备忘录模式

备忘录模式允许在不暴露对象实现细节的情况下保存和恢复对象之前的状态。

适用场景

  1. 需要创建对象状态快照来恢复其之前的状态时,可以使用备忘录模式。备忘录模式允许你复制对象中的全部状态(包括私有成员变量),并将其独立于对象进行保存。尽管大部分人因为 “撤销” 这个用例才记得该模式, 但其实它在处理事务(比如需要在出现错误时回滚一个操作)的过程中也必不可少。
  2. 当直接访问对象的成员变量、获取器或设置器将导致封装被突破时,可以使用该模式。备忘录让对象自行负责创建其状态的快照。任何其他对象都不能读取快照,这有效地保障了数据的安全性。

实现方式

  1. 确定担任原发器角色的类。重要的是明确程序使用的一个原发器中心对象,还是多个较小的对象。
  2. 创建备忘录类。逐一声明对应每个原发器成员变量的备忘录成员变量。
  3. 将备忘录类设为不可变。备忘录只能通过构造函数一次性接收数据。该类中不能包含设置器。
  4. 如果所使用的编程语言支持嵌套类,则可将备忘录嵌套在原发器中;如果不支持,那么可从备忘录类中抽取一个空接口,然后让其他所有对象通过接口来引用备忘录。可在该接口中添加一些元数据操作,但不能暴露原发器的状态。
  5. 在原发器中添加一个创建备忘录的方法。原发器必须通过备忘录构造函数的一个或多个实际参数来将自身状态传递给备忘录。
  6. 在原发器类中添加一个用于恢复自身状态的方法。该方法接受备忘录对象作为参数。如果在之前的步骤中抽取了接口,那么可将接口作为参数的类型。在这种情况下,需要将输入对象强制转换为备忘录,因为原发器需要拥有对该对象的完全访问权限。
  7. 无论负责人是命令对象、历史记录或其他完全不同的东西,它都必须要知道何时向原发器请求新的备忘录、 如何存储备忘录以及何时使用特定备忘录来对原发器进行恢复。
  8. 负责人与原发器之间的连接可以移动到备忘录类中。 在本例中,每个备忘录都必须与创建自己的原发器相连接。恢复方法也可以移动到备忘录类中,但只有当备忘录类嵌套在原发器中,或者原发器类提供了足够多的设置器并可对其状态进行重写时,这种方式才能实现。

代码实现

// 数据模型
public class CallOfDuty {

    private int mCheckPoint = 1;
    private int mLifeValue = 100;
    private String mWeapon = "沙漠之鹰";

    public void play() {
        System.out.println("玩游戏:" + String.format("第%s关", mCheckPoint) + "奋战杀敌中");
        mLifeValue -= 10;
        System.out.println("游戏进度升级");
        mCheckPoint++;
        System.out.println("到达" + String.format("第%s关", mCheckPoint));
    }

    public void quit() {
        System.out.println("----------");
        System.out.println("退出游戏前属性:" + this.toString());
        System.out.println("退出游戏");
        System.out.println("----------");
    }

    public Remark createRemark() {
        Remark remark = new Remark();
        remark.mCheckPoint = mCheckPoint;
        remark.mLifeValue = mLifeValue;
        remark.mWeapon = mWeapon;
        return remark;
    }

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

    @Override
    public String toString() {
        return "CallOfDuty{" +
                "mCheckPoint=" + mCheckPoint +
                ", mLifeValue=" + mLifeValue +
                ", mWeapon='" + mWeapon + '\'' +
                '}';
    }
}

// 备忘录类
public class Remark {
    public int mCheckPoint = 1;
    public int mLifeValue = 100;
    public String mWeapon = "沙漠之鹰";

    @Override
    public String toString() {
        return "Remark{" +
                "mCheckPoint=" + mCheckPoint +
                ", mLifeValue=" + mLifeValue +
                ", mWeapon='" + mWeapon + '\'' +
                '}';
    }
}

// 负责管理 Remark
public class Caretaker {

    Remark mRemark;

    /**
     * 存档
     * @param remark 备忘录
     */
    public void archive(Remark remark){
        this.mRemark = remark;
    }


    /**
     * 获取存档
     * @return 存档
     */
    public Remark getRemark() {
        return mRemark;
    }
}

public class DesignPatternsTest {

    @Test
    public void remarkTest() {
        // 构建游戏对象
        CallOfDuty game = new CallOfDuty();
        game.play();
        // 存档
        Caretaker caretaker = new Caretaker();
        caretaker.archive(game.createRemark());
        // 退出游戏
        game.quit();
        // 恢复游戏
        CallOfDuty newGame = new CallOfDuty();
        newGame.restore(caretaker.getRemark());
    }
}

玩游戏:第1关奋战杀敌中
游戏进度升级
到达第2关
----------
退出游戏前属性:CallOfDuty{mCheckPoint=2, mLifeValue=90, mWeapon='沙漠之鹰'}
退出游戏
----------
恢复后的游戏属性:CallOfDuty{mCheckPoint=2, mLifeValue=90, mWeapon='沙漠之鹰'}

观察者模式

观察者模式定义了一种订阅机制,可在对象事件发生时通知多个 “观察” 该对象的其他对象。

适用场景

  1. 当一个对象状态的改变需要通知其他对象,或者实际对象是事先未知的或动态变化的时,可使用观察者模式。
  2. 当应用中的一些对象必须观察其他对象时,可使用该模式。但仅能在有限时间内或特定情况下使用。

实现方式

  1. 声明订阅者接口,该接口至少声明一个 update() 方法,发布者通过调用订阅者的 update() 方法来通知更新。
  2. 声明发布者接口并定义一些接口来在订阅者列表中添加和删除订阅对象。发布者必须仅通过订阅者接口与它们进行交互。
  3. 确定存放实际订阅列表的位置并实现订阅方法。通常所有类型的发布者代码看上去都一样,因此将列表放置在直接扩展自发布者接口的抽象类中是显而易见的。具体发布者会扩展该类从而继承所有的订阅行为,但是, 如果需要在现有的类层次结构中应用该模式,则可以考虑使用组合的方式:将订阅逻辑放入一个独立的对象, 然后让所有实际订阅者使用该对象。
  4. 创建具体发布者类。每次发布者发生了重要事件时都必须通知所有的订阅者。
  5. 在具体订阅者类中实现通知更新的方法。绝大部分订阅者需要一些与事件相关的上下文数据。这些数据可作为通知方法的参数来传递。但还有另一种选择。订阅者接收到通知后直接从通知中获取所有数据。在这种情况下,发布者必须通过更新方法将自身传递出去。另一种不太灵活的方式是通过构造函数将发布者与订阅者永久性地连接起来。
  6. 客户端必须生成所需的全部订阅者,并在相应的发布者处完成注册工作。

代码实现

// 订阅者接口
public interface Observer {
    void update(Object arg);
}

// 发布者
public class Observable {
    private List<Observer> mObserverList = new ArrayList<>();

    public void addObserver(Observer observer) {
        mObserverList.add(observer);
    }

    public void removeObserver(Observer observer) {
        mObserverList.remove(observer);
    }

    public void notifyObservers(Object arg) {
        for (Observer observer : mObserverList) {
            observer.update(arg);
        }
    }
}

// 具体订阅者
public class Coder implements Observer {

    public String name;

    public Coder(String name) {
        this.name = name;
    }

    @Override
    public void update( Object arg) {
        System.out.println("Hi " + name + ", AndroidDevelopers 更新内容啦: " + arg);
    }

    @Override
    public String toString() {
        return "Coder{" +
                "name='" + name + '\'' +
                '}';
    }
}

// 具体发布者
public class AndroidDevelopers extends Observable {

    public void postNewContent(String content) {
        notifyObservers(content);
    }
}


public class DesignPatternsTest {

    @Test
    public void observerTest() {
        Coder coder1 = new Coder("李白");
        Coder coder2 = new Coder("杜甫");
        Coder coder3 = new Coder("白居易");
        Coder coder4 = new Coder("李清照");
        Coder coder5 = new Coder("温庭筠");
        Coder coder6 = new Coder("文天祥");
        AndroidDevelopers androidDevelopers = new AndroidDevelopers();
        androidDevelopers.addObserver(coder1);
        androidDevelopers.addObserver(coder2);
        androidDevelopers.addObserver(coder3);
        androidDevelopers.addObserver(coder4);
        androidDevelopers.addObserver(coder5);
        androidDevelopers.addObserver(coder6);
        androidDevelopers.postNewContent("Compose 起步 ");
    }
}

Hi 李白, AndroidDevelopers 更新内容啦: Compose 起步 
Hi 杜甫, AndroidDevelopers 更新内容啦: Compose 起步 
Hi 白居易, AndroidDevelopers 更新内容啦: Compose 起步 
Hi 李清照, AndroidDevelopers 更新内容啦: Compose 起步 
Hi 温庭筠, AndroidDevelopers 更新内容啦: Compose 起步 
Hi 文天祥, AndroidDevelopers 更新内容啦: Compose 起步 

状态模式

状态模式能在一个对象的内部状态变化时改变其行为,使其看上去就像改变了自身所属的类一样。对于具体行为封装到状态之中,具体行为根据其所在状态实现不同功能。

适用场景

  1. 如果对象需要根据自身当前状态进行不同行为,同时状态的数量非常多且与状态相关的代码会频繁变更的话,可使用状态模式。
  2. 如果某个类需要根据成员变量的当前值改变自身行为,从而需要使用大量的条件语句时,可使用该模式。状态模式会将这些条件语句的分支抽取到相应状态类的方法中。同时还可以清除主要类中与特定状态相关的临时成员变量和帮手方法代码。
  3. 当相似状态和基于条件的状态机转换中存在许多重复代码时,可使用状态模式。

实现方式

  1. 确定哪些类是上下文。它可能是包含依赖于状态的代码的已有类;如果特定于状态的代码分散在多个类中, 那么它可能是一个新的类。
  2. 声明状态接口。虽然可能会需要完全复制上下文中声明的所有方法,但最好是仅把关注点放在那些可能包含特定于状态的行为的方法上。
  3. 为每个实际状态创建一个继承于状态接口的类。然后检查上下文中的方法并将与特定状态相关的所有代码抽取到新建的类中。在将代码移动到状态类的过程中, 可能会发现它依赖于上下文中的一些私有成员。 对于这种情况可采用以下几种变通方式:
    • 将这些成员变量或方法设为公有。
    • 将需要抽取的上下文行为更改为上下文中的公有方法,然后在状态类中调用。这种方式简陋却便捷, 可以稍后再对其进行修补。
    • 将状态类嵌套在上下文类中。这种方式需要你所使用的编程语言支持嵌套类。
  4. 在上下文类中添加一个状态接口类型的引用成员变量,以及一个用于修改该成员变量值的公有设置器。
  5. 再次检查上下文中的方法,将空的条件语句替换为相应的状态对象方法。
  6. 为切换上下文状态,需要创建某个状态类实例并将其传递给上下文。可以在上下文、各种状态或客户端中完成这项工作。无论在何处完成这项工作,该类都将依赖于其所实例化的具体类。

代码实现

以电视机遥控为例

// 电视机接口,定义了电视机操作行为函数
public interface TvState {

    void nextChannel();

    void prevChannel();

    void turnUp();

    void turnDown();
}

// 电视机关机状态
public class PowerOffState implements TvState {

    @Override
    public void nextChannel() {
        System.out.println("关机状态下一频道操作无效\n");
    }

    @Override
    public void prevChannel() {
        System.out.println("关机状态上一频道操作无效\n");
    }

    @Override
    public void turnUp() {
        System.out.println("关机状态调高音量操作无效\n");
    }

    @Override
    public void turnDown() {
        System.out.println("关机状态调低音量操作无效\n");
    }
}

// 电视机关机状态
public class PowerOnState implements TvState {

    @Override
    public void nextChannel() {
        System.out.println("下一频道\n");
    }

    @Override
    public void prevChannel() {
        System.out.println("上一频道\n");
    }

    @Override
    public void turnUp() {
        System.out.println("调高音量\n");
    }

    @Override
    public void turnDown() {
        System.out.println("调低音量\n");
    }
}

// 电源操作接口
public interface PowerController {

    void powerOn();

    void powerOff();

}

public class TvController implements PowerController {
    TvState mTvState;

    public void setTvState(TvState tvState) {
        mTvState = tvState;
    }

    @Override
    public void powerOn() {
        setTvState(new PowerOnState());
        System.out.println("开机啦\n");
    }

    @Override
    public void powerOff() {
        setTvState(new PowerOffState());
        System.out.println("关机啦\n");
    }

    public void nextChannel(){
        mTvState.nextChannel();
    }

    public void prevChannel(){
        mTvState.prevChannel();
    }

    public void turnUp(){
        mTvState.turnUp();
    }

    public void turnDown(){
        mTvState.turnDown();
    }
}

开机啦

下一频道

上一频道

调低音量

调高音量

关机啦

关机状态调高音量操作无效

策略模式

策略模式通过定义一系列算法,并将每种算法分别放入独立的类中,以使算法的对象能够相互替换。

适用场景

  1. 使用对象中各种不同的算法变体,并希望能在运行时切换算法时,可使用策略模式。 策略模式能够将对象关联至可以不同方式执行特定子任务的不同子对象,从而以间接方式在运行时更改对象行为。
  2. 当有许多仅在执行某些行为时略有不同的相似类时,可使用策略模式。 策略模式能将不同行为抽取到一个独立类层次结构中,并将原始类组合成同一个,从而减少重复代码。
  3. 如果算法在上下文的逻辑中不是特别重要,使用该模式能将类的业务逻辑与其算法实现细节隔离开来。
  4. 当类中使用了复杂条件运算符以在同一算法的不同变体中切换时,可使用该模式。

实现方式

  1. 从上下文类中找出修改频率较高的算法(也可能是用于在运行时选择某个算法变体的复杂条件运算符)。
  2. 声明该算法所有变体的通用策略接口。
  3. 将算法逐一抽取到各自的类中, 它们都必须实现策略接口。
  4. 在上下文类中添加一个成员变量用于保存对于策略对象的引用。 然后提供设置器以修改该成员变量。 上下文仅可通过策略接口同策略对象进行交互, 如有需要还可定义一个接口来让策略访问其数据。
  5. 客户端必须将上下文类与相应策略进行关联, 使上下文可以预期的方式完成其主要工作。

代码实现

public interface CalculateStrategy {

    int calculatePrice(int km);
}

public class SubwayStrategy implements CalculateStrategy {
    @Override
    public int calculatePrice(int km) {
        if (km < 6) {
            return 3;
        } else if (km > 6 && km < 12) {
            return 4;
        } else if (km > 12 && km < 22) {
            return 5;
        } else if (km > 22 && km < 32) {
            return 6;
        }
        return 7;
    }
}

public class BusStrategy implements CalculateStrategy {
    @Override
    public int calculatePrice(int km) {
        // 超过10公里的总背书
        int extraTotal = km - 10;
        // 超过5公里对倍数
        int extraFactor = extraTotal / 5;
        // 超过的距离对5取余
        int fraction = extraTotal % 5;
        // 加个计算
        int price = 1 + extraFactor;
        return fraction > 0 ? ++price : price;
    }
}

public class TranficCalculator {

    private CalculateStrategy mStrategy;

    public TranficCalculator() {
    }

    public void setStrategy(CalculateStrategy strategy) {
        mStrategy = strategy;
    }

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

public class DesignPatternsTest {

    @Test
    public void strategyTest() {
        TranficCalculator tranficCalculator = new TranficCalculator();
        tranficCalculator.setStrategy(new SubwayStrategy());
        int subwayPrice = tranficCalculator.calculatePrice(10);
        System.out.println("地铁10公里出行价格" + subwayPrice + "元");

        tranficCalculator.setStrategy(new BusStrategy());
        int busPrice = tranficCalculator.calculatePrice(10);
        System.out.println("地铁10公里出行价格" + busPrice + "元");
    }
}

地铁10公里出行价格4元
地铁10公里出行价格1

模版方法模式

模版方法模式在超类中定义了一个算法的框架,允许子类在不修改结构的情况下重写算法的特定步骤。

适用场景

  1. 只希望客户端扩展某个特定算法步骤,而不是整个算法或其结构时,可使用模板方法模式。
  2. 当多个类的算法除一些细微不同之外几乎完全一样时,可使用该模式。 但其后果就是,只要算法发生变化,就可能需要修改所有的类。

实现方式

  1. 分析目标算法,确定能否将其分解为多个步骤。从所有子类的角度出发,考虑哪些步骤能够通用,哪些步骤各不相同。
  2. 创建抽象基类并声明一个模板方法和代表算法步骤的一系列抽象方法。在模板方法中根据算法结构依次调用相应步骤。可用 final 最终修饰模板方法以防止子类对其进行重写。
  3. 虽然可将所有步骤全都设为抽象类型,但默认实现可能会给部分步骤带来好处,因为子类无需实现那些方法。
  4. 可考虑在算法的关键步骤之间添加钩子。
  5. 为每个算法变体新建一个具体子类,它必须实现所有的抽象步骤,也可以重写部分可选步骤。

代码实现

// 抽象的 Computer
public abstract class AbstractComputer {

    protected void powerOn(){
        System.out.println("开启电源");
    }

    protected void checkHardware(){
        System.out.println("硬件检查");
    }

    protected void loadOs(){
        System.out.println("进入操作系统");
    }

    protected void login(){
        System.out.println("小白的计算机无验证,直接进入系统");
    }

    protected void startUp(){
        System.out.println("------------    开机    ------------");
        powerOn();
        checkHardware();
        loadOs();
        login();
        System.out.println("------------    关机    ------------");
    }
}

// 程序员的计算机
public class CoderComputer  extends AbstractComputer{

    @Override
    protected void login() {
        System.out.println("程序员只需进行用户和密码验证就可启动计算机");
    }
}

// 军用计算机
public class MilitaryComputer extends AbstractComputer {

    @Override
    protected void checkHardware() {
        super.checkHardware();
        System.out.println("见哈硬件防火墙");
    }

    @Override
    protected void login() {
        System.out.println("进行指纹识别等复杂用户验证");
    }
}

public class DesignPatternsTest {

    @Test
    public void test() {
        CoderComputer coderComputer = new CoderComputer();
        coderComputer.startUp();
        MilitaryComputer militaryComputer = new MilitaryComputer();
        militaryComputer.startUp();
    }
}

------------    开机    ------------
开启电源
硬件检查
进入操作系统
程序员只需进行用户和密码验证就可启动计算机
------------    关机    ------------
------------    开机    ------------
开启电源
硬件检查
见哈硬件防火墙
进入操作系统
进行指纹识别等复杂用户验证
------------    关机    ------------

访问者模式

访问者模式能将算法与其所作用的对象隔离开来。

使用场景

  1. 如果需要对一个复杂对象结构(例如对象树)中的所有元素执行某些操作,可使用访问者模式。
  2. 可使用访问者模式来清理辅助行为的业务逻辑。
  3. 当某个行为仅在类层次结构中的一些类中有意义,而在其他类中没有意义时,可使用该模式。

实现方式

  1. 在访问者接口中声明一组 “访问” 方法, 分别对应程序中的每个具体元素类。
  2. 声明元素接口。如果程序中已有元素类层次接口,可在层次结构基类中添加抽象的 “接收” 方法。 该方法必须接受访问者对象作为参数。
  3. 在所有具体元素类中实现接收方法。这些方法必须将调用重定向到当前元素对应的访问者对象中的访问者方法上。
  4. 元素类只能通过访问者接口与访问者进行交互。不过访问者必须知晓所有的具体元素类,因为这些类在访问者方法中都被作为参数类型引用。
  5. 为每个无法在元素层次结构中实现的行为创建一个具体访问者类并实现所有的访问者方法。
  6. 客户端必须创建访问者对象并通过 “接收” 方法将其传递给元素。

代码实现

public abstract class Staff {

    public String name;
    public int kpi;

    public Staff(String name) {
        this.name = name;
        this.kpi = new Random().nextInt();
    }

    public abstract void accept(Visitor visitor);
}


public class Engineer extends Staff {

    public Engineer(String name) {
        super(name);
    }

    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }

    public int getCodeLines() {
        return new Random().nextInt(10 * 1000);
    }
}


public class Manager extends Staff {

    private int products;

    public Manager(String name) {
        super(name);
        products = new Random().nextInt(10);
    }

    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }

    public int getProducts() {
        return products;
    }
}



public interface Visitor {
    void visit(Engineer engineer);
    void visit(Manager manager);
}


public class CtoVisitor implements Visitor {
    @Override
    public void visit(Engineer engineer) {
        System.out.println("工程师:" + engineer.name + ",代码行数:" + engineer.getCodeLines());
    }

    @Override
    public void visit(Manager manager) {
        System.out.println("经理:" + manager.name + ",kpi:" + manager.kpi+",新产品数量:"+manager.getProducts());
    }
}

public class CeoVisitor implements Visitor {
    @Override
    public void visit(Engineer engineer) {
        System.out.println("工程师:" + engineer.name + ",kpi:" + engineer.kpi);
    }

    @Override
    public void visit(Manager manager) {
        System.out.println("经理:" + manager.name + ",kpi:" + manager.kpi+",新产品数量:"+manager.getProducts());
    }
}

public class BusinessReport {

    List<Staff> mStaffs = new ArrayList<>();

    public BusinessReport() {
        mStaffs.add(new Manager("王经理"));
        mStaffs.add(new Manager("崔经理"));
        mStaffs.add(new Engineer("工程师-刘备"));
        mStaffs.add(new Engineer("工程师-关羽"));
        mStaffs.add(new Engineer("工程师-张飞"));
    }

    public void showReport(Visitor visitor) {
        for (Staff staff : mStaffs) {
            staff.accept(visitor);
        }
    }
}

public class DesignPatternsTest {

    @Test
    public void visitorTest() {
        BusinessReport businessReport = new BusinessReport();
        System.out.println("----------    给 CEO 看的报表    ----------");
        businessReport.showReport(new CeoVisitor());
        System.out.println("----------    给 CTO 看的报表    ----------");
        businessReport.showReport(new CtoVisitor());
    }
}


----------    给 CEO 看的报表    ----------
经理:王经理,kpi:1098834980,新产品数量:5
经理:崔经理,kpi:-1039068197,新产品数量:3
工程师:工程师-刘备,kpi:-507271997
工程师:工程师-关羽,kpi:808473843
工程师:工程师-张飞,kpi:-231964394
----------    给 CTO 看的报表    ----------
经理:王经理,kpi:1098834980,新产品数量:5
经理:崔经理,kpi:-1039068197,新产品数量:3
工程师:工程师-刘备,代码行数:6496
工程师:工程师-关羽,代码行数:2636
工程师:工程师-张飞,代码行数:8813