命令设计模式 学习笔记

本文深入探讨了命令设计模式如何实现请求-响应模型中的松耦合,通过引入命令对象作为桥梁,使得请求者与接收者之间解耦。文中以文件系统操作和电灯控制为例,详细解释了命令模式的角色(Invoker、Command、Receiver)及其实现。此外,还讨论了命令模式在JUnit和线程中的应用,并指出了其扩展性和可能的代码复杂性问题。
摘要由CSDN通过智能技术生成

GoF原文

Encapsulate a request as an object,thereby letting you parameterize clients with different requests,queue or log requests,and support undoable operations.

Command design pattern is used to implement loose coupling in a request-response model.
命令设计模式用于 在请求-响应模型中实现松耦合。

一直对松耦合不懂,后来慢慢慢有了点体会,以下为理解过程
请求
具体响应
请求和响应是一对一,在一个类里,要是改动请求或者响应,就要动整个类

请求
命令

命令
具体响应

现在是请求和响应中间有个命令作为桥梁,请求改变就改请求,响应改变就改响应,彼此不影响

以上就是我理解的请求-响应模型实现松耦合

在这里插入图片描述
这篇文章的抽象挺有意思设计模式之命令模式

命令模式(Command Pattern):将军发布命令,士兵去执行

定义了三种角色:

Invoker 是调用者(将军)
Command 是命令
Receiver 是被调用者(士兵):英文翻译应该为接受者,应该本意就是接受命令,文化差异翻译成被调用者而已。
在这里插入图片描述

命令模式的应用场景

当系统的某项操作具备命令语义,且命令实现不稳定(变化)时,可以通过命令模式解耦请求与实现,使用抽象命令接口使请求方的代码架构稳定,封装接收方具体命令的实现细节。
接收方与抽象命令接口呈现弱耦合(内部方法无须一致),具备良好的扩展性。

命令模式主要适用于以下应用场景。
(1)现实语义中具备“命令”的操作(如命令菜单、Shell命令等)。
(2)请求的调用者和接收者需要解耦,使得调用者和接收者不直接交互。
(3)需要抽象出等待执行的行为,比如撤销(Undo)操作和恢复(Redo)等操作。
(4)需要支持命令宏(即命令组合操作)。
在这里插入图片描述
In command pattern, the request is send to the invoker and invoker pass it to the encapsulated command object. Command object passes the request to the appropriate method of Receiver to perform the specific action.
在命令模式中,请求发送给invoker ,invoker 将其传递给封装的 command object。
command object 将请求传递给 Receiver 的适当方法,以执行特定的操作。

接收者角色(Receiver):

该类负责具体实施或执行一个请求。

//定义个小兵,然后让小兵实现几个技能
public interface A1_FileSystemReceiver {
	void openFile();
	void writeFile();
	void closeFile();
}
ublic class A2_UnixFileSystemReceiver implements A1_FileSystemReceiver {
	@Override
	public void openFile() {
		System.out.println("Opening file in unix OS");
	}
	@Override
	public void writeFile() {
		System.out.println("Writing file in unix OS");
	}
	@Override
	public void closeFile() {
		System.out.println("Closing file in unix OS");
	}
}
public class A3_WindowsFileSystemReceiver implements A1_FileSystemReceiver {
	@Override
	public void openFile() {
		System.out.println("Opening file in Windows OS");
	}
	@Override
	public void writeFile() {
		System.out.println("Writing file in Windows OS");
	}
	@Override
	public void closeFile() {
		System.out.println("Closing file in Windows OS");
	}
}

命令角色(ICommand):

定义需要执行的所有命令行为。
We can use interface or abstract class to create our base Command, it’s a design decision and depends on your requirement.
We are going with interface because we don’t have any default implementations.
base Command 可以用接口和抽象方法,如果没有common 或者 default implementations就用接口就可以了

public interface A4_Command {
	void execute();
}

具体命令角色(ConcreteCommand):

该类内部维护一个Receiver,在其execute()方法中调用Receiver的相关方法。

public class A5_OpenFileCommand implements A4_Command {
	private A1_FileSystemReceiver fileSystem;
	// 指定哪个小兵做事
	public A5_OpenFileCommand(A1_FileSystemReceiver fs){
		this.fileSystem=fs;
	}
	
	//指定小兵做什么事
	@Override
	public void execute() {
		this.fileSystem.openFile();
	}
}
public class A6_WriteFileCommand implements A4_Command {
	private A1_FileSystemReceiver fileSystem;
	public A6_WriteFileCommand(A1_FileSystemReceiver fs){
		this.fileSystem=fs;
	}
	@Override
	public void execute() {
		this.fileSystem.writeFile();
	}
}
public class A7_CloseFileCommand implements A4_Command {
	private A1_FileSystemReceiver fileSystem;
	public A7_CloseFileCommand(A1_FileSystemReceiver fs){
		this.fileSystem=fs;
	}
	@Override
	public void execute() {
		this.fileSystem.closeFile();
	}
}

请求者角色(Invoker):

接收客户端的命令,并执行命令。
Invoker是一个简单的类,封装Command,并将请求传递给Command对象来处理请求。

public class A8_FileInvoker {
	public A4_Command command;
	// 给将军颁布圣旨
	public A8_FileInvoker(A4_Command c){
		this.command=c;
	}
	
	// 将军发号施令
	public void execute(){
		this.command.execute();
	}
}

provide a utility method to create the appropriate FileSystemReceiver object.Since we can use System class to get the operating system information, we will use this or else we can use Factory pattern to return appropriate type based on the input.

	public static A1_FileSystemReceiver getUnderlyingFileSystem(){
		 String osName = System.getProperty("os.name");
		 System.out.println("Underlying OS is:"+osName);
		 if(osName.contains("Windows")){
			 return new A3_WindowsFileSystemReceiver();
		 }else{
			 return new A2_UnixFileSystemReceiver();
		 }
	}

Let’s move now to create our command pattern example client program that will consume our file system utility.

	public static void main(String[] args) {
		//Creating the receiver object
		A1_FileSystemReceiver fileSystem = A9_FileSystemReceiverUtil.getUnderlyingFileSystem();
		
		//creating command and associating with receiver
		A5_OpenFileCommand openFileCommand = new A5_OpenFileCommand(fileSystem);
		//Creating invoker and associating with Command
		A8_FileInvoker fileInvoker = new A8_FileInvoker(openFileCommand);
		//perform action on invoker object
		fileInvoker.execute();
		
		A6_WriteFileCommand writeFileCommand = new A6_WriteFileCommand(fileSystem);
		fileInvoker = new A8_FileInvoker(writeFileCommand);
		fileInvoker.execute();
		
		A7_CloseFileCommand closeFileCommand = new A7_CloseFileCommand(fileSystem);
		fileInvoker = new A8_FileInvoker(closeFileCommand);
		fileInvoker.execute();
	}

结果

Underlying OS is:Windows 7
Opening file in Windows OS
Writing file in Windows OS
Closing file in Windows OS

在这里插入图片描述看一下第二个例子
Command Pattern Receiver Classes

public class B05_LightReceiver {
	public void on() {
		System.out.println("  电灯打开了.. ");
	}
	public void off() {
		System.out.println("  电灯关闭了.. ");
	}
}

Command Pattern Interface and Implementations

//创建命令接口
public interface B01_Command {
	// 执行动作(操作)
	public void execute();
	// 撤销动作(操作)
	public void undo();

}
public class B02_LightOffCommand implements B01_Command {
	// 聚合 LightReceiver
	B05_LightReceiver light;
	// 构造器
	public B02_LightOffCommand(B05_LightReceiver light) {
		this.light = light;
	}
	@Override
	public void execute() {
		// 调用接收者的方法
		light.off();
	}
	@Override
	public void undo() {
		// 调用接收者的方法
		light.on();
	}
}
public class B03_LightOnCommand implements B01_Command {
	// 聚合 LightReceiver
	B05_LightReceiver light;
	// 构造器
	public B03_LightOnCommand(B05_LightReceiver light) {
		this.light = light;
	}
	@Override
	public void execute() {
		// 调用接收者的方法
		light.on();
	}
	@Override
	public void undo() {
		// 调用接收者的方法
		light.off();
	}
}
public class B04_NoCommand implements B01_Command {
	@Override
	public void execute() {
	}
	@Override
	public void undo() {
	}
}

Command Pattern Invoker/Controller Class

public class B06_RemoteController {
 
    // 开关 按钮的命令数组
    B01_Command[] onCommands;
    B01_Command[] offCommands;
    //执行撤销的命令
    B01_Command undoCommand;
 
    public B06_RemoteController() {
        onCommands = new B01_Command[5];
        offCommands = new B01_Command[5];
        for (int i = 0; i < 5; i++) {
            onCommands[i] = new B04_NoCommand();
            offCommands[i] = new B04_NoCommand();
        }
    }
 
    // 给我们的按钮设置你需要的命令
    public void setCommand(int no, B01_Command onCommand, B01_Command offCommand) {
        onCommands[no] = onCommand;
        offCommands[no] = offCommand;
    }
 
    // 按下开按钮
    public void onButtonWasPushed(int no) {
        // 找到你按下的开的按钮,并调用对应方法
        onCommands[no].execute();
        //记录这次的操作,用于撤销
        undoCommand = onCommands[no];
 
    }
 
    // 按下开按钮
    public void offButtonWasPushed(int no) {
        // 找到你按下的关的按钮,并调用对应方法
        offCommands[no].execute();
        // 记录这次的操作,用于撤销
        undoCommand = offCommands[no];
    }
 
    // 按下撤销按钮
    public void undoButtonWasPushed() {
        undoCommand.undo();
    }
}

client program

    public static void main(String[] args) {
 
        //创建电灯的对象(接受者)
        B05_LightReceiver lightReceiver = new B05_LightReceiver();
 
        //创建电灯相关的开关命令
        B03_LightOnCommand lightOnCommand = new B03_LightOnCommand(lightReceiver);
        B02_LightOffCommand lightOffCommand = new B02_LightOffCommand(lightReceiver);
 
        //需要一个遥控器
        B06_RemoteController remoteController = new B06_RemoteController();
 
        //给我们的遥控器设置命令, 比如 no = 0 是电灯的开和关的操作
        remoteController.setCommand(0, lightOnCommand, lightOffCommand);
 
        System.out.println("--------   按下灯的开按钮    -----------");
        remoteController.onButtonWasPushed(0);
        System.out.println("--------   按下灯的关按钮    -----------");
        remoteController.offButtonWasPushed(0);
        System.out.println("--------   按下撤销按钮-----------  ");
        remoteController.undoButtonWasPushed();
    }

在这里插入图片描述
Important Points

Receiver implementation is separate from command implementation.
接收方实现与命令实现是分开的。

Command is the core of command design pattern that defines the contract for implementation.
命令是命令设计模式的核心,它定义了实现的契约。

Command implementation classes chose the method to invoke on receiver object, for every method in receiver there will be a command implementation. It works as a bridge between receiver and action methods.
命令实现类选择接收器对象上的方法,对于 receiver object中的每个方法都有一个命令实现。
它是receiver 和 action methods之间的桥梁。

Invoker class just forward the request from client to the command object.
调用者类只是将请求从客户机转发到命令对象。

Client is responsible to instantiate appropriate command and receiver implementation and then associate them together.
客户端负责实例化适当的命令和接收器实现,然后将它们关联在一起。

Client is also responsible for instantiating invoker object and associating command object with it and execute the action method.
客户端还负责实例化调用者对象,并将命令对象与之关联,并执行操作方法。

Command design pattern is easily extendible, we can add new action methods in receivers and create new Command implementations without changing the client code.
命令设计模式很容易扩展,我们可以在接收端添加新的操作方法并创建新的命令实现,而不需要更改客户端代码。

The drawback with Command design pattern is that the code gets huge and confusing with high number of action methods and because of so many associations.
Command设计模式的缺点是代码会变得非常庞大,并且会因为大量的操作方法和太多的关联而产生混淆。
在这里插入图片描述

命令模式在JDK源码中的应用

首先来看JDK中的Runnable接口,Runnable相当于命令的抽象,只要是实现了Runnable接口的类都被认为是一个线程。

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

实际上调用线程的start()方法之后,就有资格去抢CPU资源,而不需要编写获得CPU资源的逻辑。而线程抢到CPU资源后,就会执行run()方法中的内容,用Runnable接口把用户请求和CPU执行进行解耦

命令模式在JUnit源码中的应用

再来看一个大家非常熟悉的junit.framework.Test接口。

public interface Test {

	public abstract int countTestCases();

	public abstract void run(TestResult result);
}

Test接口中有两个方法:
第一个是countTestCases()方法,用来统计当前需要执行的测试用例总数。
第二个是run()方法,用来执行具体的测试逻辑,其参数TestResult是用来返回测试结果的。

实际上,我们在平时编写测试用例的时候,只需要实现Test接口就被认为是一个测试用例,那么在执行的时候就会被自动识别。通常做法都是继承TestCase类,不妨来看一下TestCase的源码。

public abstract class TestCase extends Assert implements Test {
...
	public void run(TestResult result) {
		result.run(this);
	}
...

实际上,TestCase类也实现了Test接口。我们继承TestCase类,相当于也实现了Test接口,自然就会被扫描成为一个测试用例。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值