设计模式 ~ 结构型模式 ~ 命令模式 ~ Command Pattern。

设计模式 ~ 结构型模式 ~ 命令模式 ~ Command Pattern。



概述。

定义。

将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行存储、传递、调用、增加与管理。



结构。

命令模式包含以下主要角色。

  • 抽象命令类(Command)角色。
    定义命令的接口,声明执行的方法。

  • 具体命令(Concrete Command)角色。
    具体的命令,实现命令接口;通常会持有接收者,并调用接收者的功能来完成命令要执行的操作。

  • 实现者/接收者(Receiver)角色。
    接收者,真正执行命令的对象。任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能。

  • 调用者/请求者(Invoker)角色。
    要求命令对象执行请求,通常会持有命令对象,可以持有很多的命令对象。这个是客户端真正触发命令并要求命令执行相应操作的地方,也就是说相当于使用命令对象的入口。



案例实现。

将上面的案例用代码实现,那我们就需要分析命令模式的角色在该案例中由谁来充当。

服务员: 就是调用者角色,由她来发起命令。

资深大厨: 就是接收者角色,真正命令执行的对象。

订单: 命令中包含订单。

类图如下。

在这里插入图片描述
代码如下。

package com.geek.command.pattern;

import lombok.Data;

import java.util.HashMap;
import java.util.Map;

/**
 * 订单类。
 *
 * @author geek
 */
@Data
public class Order {

    /**
     * 餐桌号码。
     */
    private Integer diningTable;

    /**
     * 餐品及份数。
     */
    private Map<String, Integer> foodMap = new HashMap<String, Integer>();

}

package com.geek.command.pattern;

/**
 * 资深大厨类 ~ 命令的 Receiver。
 *
 * @author geek
 */
public class SeniorChef {

    public void makeFood(String foodName, Integer num) {
        System.out.println(num + " 份" + foodName);
    }

}

package com.geek.command.pattern;

/**
 * 接口 ~ 抽象命令。
 *
 * @author geek
 */
public interface ICommand {

    /**
     * 只需要定义一个统一的执行方法。
     */
    void execute();

}

package com.geek.command.pattern;

import lombok.AllArgsConstructor;
import lombok.Data;

import java.util.Set;

/**
 * 具体的命令类。
 *
 * @author geek
 */
@Data
@AllArgsConstructor
public class OrderCommand implements ICommand {

    /**
     * 持有接受者对象。
     */
    private SeniorChef receiver;
    private Order order;

    public void execute() {
        System.out.println(this.order.getDiningTable() + " 桌的订单:");
        Set<String> keys = this.order.getFoodMap().keySet();
        for (String key : keys) {
            this.receiver.makeFood(key, this.order.getFoodMap().get(key));
        }

        try {
            // 停顿一下,模拟做饭的过程。
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(this.order.getDiningTable() + " 桌的饭准备好了。");
    }

}

package com.geek.command.pattern;

import java.util.ArrayList;
import java.util.List;

/**
 * 服务员类 ~ 请求者角色。
 *
 * @author geek
 */
public class Waiter {

    /**
     * 可以持有很多的命令对象。
     */
    private final List<ICommand> commandList;

    public Waiter() {
        commandList = new ArrayList<>();
    }

    public void setCommand(ICommand cmd) {
        // 将 command 对象存储到 list 集合中。
        this.commandList.add(cmd);
    }

    /**
     * 发出命令 喊 订单来了,厨师开始执行。
     */
    public void orderUp() {
        System.out.println("美女服务员:叮咚,大厨,新订单来了。");
        for (ICommand cmd : this.commandList) {
            if (cmd != null) {
                cmd.execute();
            }
        }
    }

}

package com.geek.command.pattern;

/**
 * @author geek
 */
public class Client {

    public static void main(String[] args) {
        // 创建 2 个 order。
        Order order1 = new Order();
        order1.setDiningTable(1);
        order1.getFoodMap().put("西红柿鸡蛋面", 1);
        order1.getFoodMap().put("小杯可乐", 2);

        Order order2 = new Order();
        order2.setDiningTable(3);
        order2.getFoodMap().put("尖椒肉丝盖饭", 1);
        order2.getFoodMap().put("小杯雪碧", 1);

        // 创建接收者。
        SeniorChef receiver = new SeniorChef();
        // 将订单和接收者封装成命令对象。
        OrderCommand cmd1 = new OrderCommand(receiver, order1);
        OrderCommand cmd2 = new OrderCommand(receiver, order2);
        // 创建调用者 waiter。
        Waiter invoker = new Waiter();
        invoker.setCommand(cmd1);
        invoker.setCommand(cmd2);

        // 将订单带到柜台,并向厨师喊:订单来了。
        invoker.orderUp();
    }

}

/*
Connected to the target VM, address: '127.0.0.1:65004', transport: 'socket'
美女服务员:叮咚,大厨,新订单来了。
1 桌的订单:
1 份西红柿鸡蛋面
2 份小杯可乐
1 桌的饭准备好了。
3 桌的订单:
1 份尖椒肉丝盖饭
1 份小杯雪碧
3 桌的饭准备好了。
Disconnected from the target VM, address: '127.0.0.1:65004', transport: 'socket'

Process finished with exit code 0

 */



优缺点。

优点。

  • 降低系统的耦合度。命令模式能将调用操作的对象与实现该操作的对象解耦。

  • 增加或删除命令非常方便。采用命令模式增加与删除命令不会影响其他类,它满足“开闭原则”,对扩展比较灵活。

  • 可以实现宏命令。命令模式可以与组合模式结合,将多个命令装配成一个组合命令,即宏命令。

  • 方便实现 Undo 和 Redo 操作。命令模式可以与后面介绍的备忘录模式结合,实现命令的撤销与恢复。

缺点。

  • 使用命令模式可能会导致某些系统有过多的具体命令类。

  • 系统结构更加复杂。



使用场景。

  • 系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。

  • 系统需要在不同的时间指定请求、将请求排队和执行请求。

  • 系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。



JDK 源码解析。

Runable 是一个典型命令模式,Runnable 担当命令的角色,Thread 充当的是调用者,start 方法就是其执行方法。

Runnable // 命令接口,抽象命令角色。

/*
 * Copyright (c) 1994, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package java.lang;

/**
 * The <code>Runnable</code> interface should be implemented by any
 * class whose instances are intended to be executed by a thread. The
 * class must define a method of no arguments called <code>run</code>.
 * <p>
 * This interface is designed to provide a common protocol for objects that
 * wish to execute code while they are active. For example,
 * <code>Runnable</code> is implemented by class <code>Thread</code>.
 * Being active simply means that a thread has been started and has not
 * yet been stopped.
 * <p>
 * In addition, <code>Runnable</code> provides the means for a class to be
 * active while not subclassing <code>Thread</code>. A class that implements
 * <code>Runnable</code> can run without subclassing <code>Thread</code>
 * by instantiating a <code>Thread</code> instance and passing itself in
 * as the target.  In most cases, the <code>Runnable</code> interface should
 * be used if you are only planning to override the <code>run()</code>
 * method and no other <code>Thread</code> methods.
 * This is important because classes should not be subclassed
 * unless the programmer intends on modifying or enhancing the fundamental
 * behavior of the class.
 *
 * @author  Arthur van Hoff
 * @see     java.lang.Thread
 * @see     java.util.concurrent.Callable
 * @since   JDK1.0
 */
@FunctionalInterface
public interface Runnable {
    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();
}
//调用者。
public
class Thread implements Runnable {

...

    /* What will be run. */
    private Runnable target;

...

  
    public synchronized void start() {
        /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }

    private native void start0();

。。。

}

会调用一个 native 方法 start0(); 调用系统方法,开启一个线程。而接收者是对程序员开放的,可以自己定义接收者。

package com.geek.command.pattern.demo;

/**
 * jdk Runnable 命令模式。
 * TurnOffThread ~ 属于具体命令角色。
 *
 * @author geek
 */
public class TurnOffThread implements Runnable {

    private final Receiver receiver;

    public TurnOffThread(Receiver receiver) {
        this.receiver = receiver;
    }

    public void run() {
        this.receiver.turnOff();
    }

}

package com.geek.command.pattern.demo;

/**
 * @author geek
 */
public class Receiver {

    public void turnOff() {
        System.out.println("turnOff.");
    }

}

package com.geek.command.pattern.demo;

/**
 * 测试类。
 *
 * @author geek
 */
public class Demo {

    public static void main(String[] args) {
        Receiver receiver = new Receiver();
        TurnOffThread turnOffThread = new TurnOffThread(receiver);
        Thread thread = new Thread(turnOffThread);
        thread.start();
    }

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lyfGeek

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值