命令模式详解:设计原则、应用场景与最佳实践

这是文章《命令设计模式》的第1部分(共1部分)。

命令模式是行为设计模式之一。它主要用于在请求-响应模型中实现松耦合。

命令模式详解

在命令模式中,请求被发送给调用者(Invoker),调用者将其传递给封装好的命令对象(Command)。命令对象随后将请求转发给接收者(Receiver)的适当方法,以执行特定的操作。客户端程序(Client)首先创建接收者对象,并将其绑定到命令对象上。接着,创建调用者对象,并将命令对象附加到调用者上以执行操作。现在,当客户端程序执行操作时,它会根据命令和接收者对象进行处理。

命令设计模式实例分析

我们将通过一个实际场景来演示命令模式的实现。假设我们需要开发一个文件系统工具,该工具能够执行打开、写入和关闭文件等操作,并且需要支持多种操作系统,例如Windows和Unix。为了实现这个文件系统工具,我们首先需要创建接收者类,它将实际执行所有文件操作。由于我们在Java中习惯使用接口进行编程,我们可以定义一个FileSystemReceiver接口,并为其在Windows、Unix、Solaris等不同操作系统上提供具体的实现类。

命令模式接收者类实现

package com.Olivia.design.command;

public interface FileSystemReceiver {
	void openFile();
	void writeFile();
	void closeFile();
}

FileSystemReceiver接口定义了实现类的契约。为简化示例,我创建了两个接收者类,分别用于Unix和Windows系统。

package com.Olivia.design.command;

public class UnixFileSystemReceiver implements FileSystemReceiver {

	@Override
	public void openFile() {
		System.out.println("在Unix操作系统中打开文件");
	}

	@Override
	public void writeFile() {
		System.out.println("在Unix操作系统中写入文件");
	}

	@Override
	public void closeFile() {
		System.out.println("在Unix操作系统中关闭文件");
	}
}
package com.Olivia.design.command;

public class WindowsFileSystemReceiver implements FileSystemReceiver {

	@Override
	public void openFile() {
		System.out.println("在Windows操作系统中打开文件");
	}

	@Override
	public void writeFile() {
		System.out.println("在Windows操作系统中写入文件");
	}

	@Override
	public void closeFile() {
		System.out.println("在Windows操作系统中关闭文件");
	}
}

你是否注意到了@Override注解?如果你想了解它的用途,可以查阅有关Java注解及其好处的资料。现在,我们的接收者类已经准备就绪,可以开始实现命令类了。

命令模式接口与实现方法

我们可以使用接口或抽象类来创建基础命令,这是一个设计决策,取决于你的具体需求。我们选择使用接口,因为我们没有任何默认实现。

package com.Olivia.design.command;

public interface Command {
	void execute();
}

现在,我们需要为接收者执行的所有不同类型的操作创建实现。由于我们有三个操作,我们将创建三个命令实现。每个命令实现将请求转发给接收者的相应方法。

package com.Olivia.design.command;

public class OpenFileCommand implements Command {

	private FileSystemReceiver fileSystem;
	
	public OpenFileCommand(FileSystemReceiver fs){
		this.fileSystem = fs;
	}
	@Override
	public void execute() {
		// open命令将请求转发给openFile方法
		this.fileSystem.openFile();
	}
}
package com.Olivia.design.command;

public class CloseFileCommand implements Command {

	private FileSystemReceiver fileSystem;
	
	public CloseFileCommand(FileSystemReceiver fs){
		this.fileSystem = fs;
	}
	@Override
	public void execute() {
		this.fileSystem.closeFile();
	}
}
package com.Olivia.design.command;

public class WriteFileCommand implements Command {

	private FileSystemReceiver fileSystem;
	
	public WriteFileCommand(FileSystemReceiver fs){
		this.fileSystem = fs;
	}
	@Override
	public void execute() {
		this.fileSystem.writeFile();
	}
}

现在我们已经准备好了接收者和命令的实现,因此可以开始实现调用者类。

命令模式调用者类

调用者(Invoker)是一个简单的类,它封装了命令并将请求传递给命令对象来处理。

package com.Olivia.design.command;

public class FileInvoker {

	public Command command;
	
	public FileInvoker(Command c){
		this.command = c;
	}
	
	public void execute(){
		this.command.execute();
	}
}

我们的文件系统工具实现已经准备就绪,我们可以开始编写一个简单的命令模式客户端程序。但在此之前,我将提供一个实用方法来创建适当的FileSystemReceiver对象。由于我们可以使用System类来获取操作系统信息,我们将利用它;否则,我们可以使用工厂模式根据输入返回适当的类型。

package com.Olivia.design.command;

public class FileSystemReceiverUtil {
	
	public static FileSystemReceiver getUnderlyingFileSystem(){
		 String osName = System.getProperty("os.name");
		 System.out.println("底层操作系统是:" + osName);
		 if(osName.contains("Windows")){
			 return new WindowsFileSystemReceiver();
		 }else{
			 return new UnixFileSystemReceiver();
		 }
	}
}

现在让我们转向创建一个使用我们文件系统工具的命令模式示例客户端程序。

package com.Olivia.design.command;

public class FileSystemClient {

	public static void main(String[] args) {
		// 创建接收者对象
		FileSystemReceiver fs = FileSystemReceiverUtil.getUnderlyingFileSystem();
		
		// 创建命令并与接收者关联
		OpenFileCommand openFileCommand = new OpenFileCommand(fs);
		
		// 创建调用者并与命令关联
		FileInvoker file = new FileInvoker(openFileCommand);
		
		// 在调用者对象上执行操作
		file.execute();
		
		WriteFileCommand writeFileCommand = new WriteFileCommand(fs);
		file = new FileInvoker(writeFileCommand);
		file.execute();
		
		CloseFileCommand closeFileCommand = new CloseFileCommand(fs);
		file = new FileInvoker(closeFileCommand);
		file.execute();
	}
}

请注意,客户端负责创建适当类型的命令对象。例如,如果您想要写入文件,则不应该创建CloseFileCommand对象。客户端程序还负责将接收者附加到命令,然后将命令附加到调用者类。上述命令模式示例程序的输出为:

底层操作系统是:Mac OS X
在Unix操作系统中打开文件
在Unix操作系统中写入文件
在Unix操作系统中关闭文件

命令模式类图

以下是我们文件系统工具实现的类图。

命令模式类图

命令模式的重要要点

  • 命令(Command)是命令设计模式的核心,它定义了实现契约。
  • 接收者(Receiver)的实现与命令的实现是分离的。
  • 命令实现类选择在接收者对象上调用的方法,接收者中的每个方法都会有一个对应的命令实现。它充当接收者和动作方法之间的桥梁。
  • 调用者(Invoker)类仅将请求从客户端转发给命令对象。
  • 客户端(Client)负责实例化适当的命令和接收者实现,然后将它们关联起来。
  • 客户端还负责实例化调用者对象,并将命令对象与其关联,然后执行动作方法。
  • 命令设计模式易于扩展,我们可以在不更改客户端代码的情况下,在接收者中添加新的动作方法并创建新的命令实现。
  • 命令设计模式的缺点是,当动作方法数量很多时,代码会变得庞大且复杂,并且存在大量的关联。

命令设计模式JDK示例

Java中的Runnable接口(java.lang.Runnable)和Swing的Action接口(javax.swing.Action)都使用了命令模式。

bannerAds