设计模式学习(五)——命令模式
定义
将“请求”封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。支持可撤销的操作。
特点
- 将发出请求的对象和执行请求的对象解耦。
- 通过command对象连接请求调用者与被调用者。
- 通过setCommand()方法改变调用者具体的执行体。
- 不同的command对象可以拥有不同的执行实体。
- 宏命令方式可以动态处理一系列的请求。
- 支持undo撤销。
实际中的应用
- 事务:借助堆栈来记录操作过程,然后逐一回滚到checkpoint
- 日志系统:如数据库的binlog,如果每一个操作都要备份整个数据库这工作量太大了。不妨记录日志,还原时可以从上个备份点开始逐一执行日志记录的动作。
- 线程池:将命令放入队列,线程池中的固定数量线程去队列里消费。
举例
场景
一个码农拥有一个音乐播放器和一台MacbookPro。它想一键执行或撤销如下等指令:
- 在MacbookPro上打开网易云音乐客户端
- 在MacbookPro上打开Chrome客户端
- 调高音乐播放器的声音
- 调低音乐播放器的声音
- ……
实现代码:
(1) MacbookPro
public class MacbookPro {
public void openChrome(){
System.out.println("open chrome.");
}
public void closeChrome(){
System.out.println("close chrome");
}
public void openNeteaseMusic(){
System.out.println("open netease music.");
}
public void closeNeteaseMusic(){
System.out.println("close netease music.");
}
}
(2) MusicPlayer
public class MusicPlayer {
public static final String HIGH = "high";
public static final String MEDIUM = "medium";
public static final String LOW = "low";
private String voice;
public MusicPlayer(){
voice = MEDIUM;
}
public String getVoice() {
return voice;
}
public void setVoice(String voice) {
this.voice = voice;
}
public void up(){
String currentVoice = getVoice();
switch (currentVoice) {
case LOW:
setVoice(MEDIUM);
break;
case MEDIUM:
case HIGH:
default:
setVoice(HIGH);
}
System.out.println("Voice changed from " + currentVoice + " to " + getVoice());
}
public void down(){
String currentVoice = getVoice();
switch (currentVoice) {
case HIGH:
setVoice(MEDIUM);
break;
case MEDIUM:
case LOW:
default:
setVoice(LOW);
}
System.out.println("Voice changed from " + currentVoice + " to " + getVoice());
}
}
(3) Command接口
public interface Command {
public void execute();
public void undo();
}
(4) OpenChromeCommand
public class OpenChromeCommand implements Command{
MacbookPro macbookPro;
public OpenChromeCommand(MacbookPro macbookPro) {
this.macbookPro = macbookPro;
}
public void execute() {
macbookPro.openChrome();
}
public void undo() {
macbookPro.closeChrome();
}
}
(5) OpenNeteaseMusicCommand
public class OpenNeteaseMusicCommand implements Command{
MacbookPro macbookPro;
public OpenNeteaseMusicCommand(MacbookPro macbookPro) {
this.macbookPro = macbookPro;
}
public void execute() {
macbookPro.openNeteaseMusic();
}
public void undo() {
macbookPro.closeNeteaseMusic();
}
}
(6) UpVoiceCommand
public class UpVoiceCommand implements Command {
MusicPlayer musicPlayer;
String preVoice;
public UpVoiceCommand(MusicPlayer musicPlayer) {
this.musicPlayer = musicPlayer;
preVoice = musicPlayer.getVoice();
}
public void execute() {
preVoice = musicPlayer.getVoice();
musicPlayer.up();
}
public void undo() {
musicPlayer.setVoice(preVoice);
System.out.println("Voice changed back to " + musicPlayer.getVoice());
}
}
(7) DownVoiceCommand
public class DownVoiceCommand implements Command {
MusicPlayer musicPlayer;
String preVoice;
public DownVoiceCommand(MusicPlayer musicPlayer) {
this.musicPlayer = musicPlayer;
preVoice = musicPlayer.getVoice();
}
public void execute() {
preVoice = musicPlayer.getVoice();
musicPlayer.down();
}
public void undo() {
musicPlayer.setVoice(preVoice);
System.out.println("Voice changed back to " + musicPlayer.getVoice());
}
}
(8) Coder
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class Coder {
List<Command> commands;
String coderName;
public Coder(String coderName){
commands = new ArrayList<>();
this.coderName = coderName;
}
public String getCoderName() {
return coderName;
}
public void setCoderName(String coderName) {
this.coderName = coderName;
}
public void addCommand(Command command){
if (!commands.contains(command)){
this.commands.add(command);
}
}
public void deleteCommamd(Command command){
this.commands.remove(command);
}
public void setCommand(int index, Command command){
if (commands.size()>index){
commands.set(index, command);
}
}
public void replaceCommand(Command preCommand, Command curCommand){
int index=commands.indexOf(preCommand);
if ( index>=0 ){
setCommand(index, curCommand);
}
}
public void clearCommands(){
commands.clear();
}
public void execute(){
Iterator<Command> iterator = commands.iterator();
if (!iterator.hasNext()){
System.out.println("No command has been added.");
return;
}
while (iterator.hasNext()){
Command command = iterator.next();
command.execute();
}
}
public void undo(){
Iterator<Command> iterator = commands.iterator();
while (iterator.hasNext()){
Command command = iterator.next();
command.undo();
}
}
public void print(){
StringBuffer stringBuffer = new StringBuffer();
Iterator<Command> iterator = commands.iterator();
while (iterator.hasNext()){
Command command = iterator.next();
stringBuffer.append(command.getClass().getSimpleName() + "-");
}
stringBuffer.deleteCharAt(stringBuffer.length()-1);
System.out.println(stringBuffer.toString());
}
}
(9) Main测试类
public class Main {
public static void main(String[] args) {
Coder coder = new Coder("Nisir");
System.out.println(coder.getCoderName());
MacbookPro macbookPro = new MacbookPro();
MusicPlayer musicPlayer = new MusicPlayer();
Command OpenChromeCommand = new OpenChromeCommand(macbookPro);
Command OpenNeteaseMusicCommand = new OpenNeteaseMusicCommand(macbookPro);
Command upVoiceCommand = new UpVoiceCommand(musicPlayer);
Command downVoiceCommand = new DownVoiceCommand(musicPlayer);
System.out.println("=============Dividing Line1=============");
coder.addCommand(OpenChromeCommand);
coder.execute();
coder.undo();
System.out.println("=============Dividing Line2=============");
coder.setCommand(0, OpenNeteaseMusicCommand);
coder.execute();
coder.undo();
System.out.println("=============Dividing Line3=============");
coder.addCommand(OpenChromeCommand);
coder.execute();
coder.undo();
System.out.println("=============Dividing Line4=============");
coder.clearCommands();
coder.execute();
coder.undo();
System.out.println("=============Dividing Line5=============");
coder.addCommand(upVoiceCommand);
coder.execute();
System.out.println("=============Dividing Line6=============");
coder.replaceCommand(upVoiceCommand, downVoiceCommand);
coder.execute();
coder.undo();
}
}
(10) 测试结果:
Nisir
=============Dividing Line1=============
open chrome.
close chrome
=============Dividing Line2=============
open netease music.
close netease music.
=============Dividing Line3=============
open netease music.
open chrome.
close netease music.
close chrome
=============Dividing Line4=============
No command has been added.
=============Dividing Line5=============
Voice changed from medium to high
=============Dividing Line6=============
Voice changed from high to medium
Voice changed back to high
小结
可以看到,coder程序员不需要知道它要调用的具体对象,它只要通过执行不同command对象,就可以去调用具体的执行实体。并且command对象还可以改变。此外,通过ArrayList还可以动态增减命令集合,以后如果有新的命令加入,可以动态扩展,一键执行!
Azkaban中的命令模式
(1) EventListener——Command
package azkaban.event;
public interface EventListener {
public void handleEvent(Event event);
}
(2) EventHandler——Coder
package azkaban.event;
import java.util.ArrayList;
import java.util.HashSet;
public class EventHandler {
private HashSet<EventListener> listeners = new HashSet<EventListener>();
public EventHandler() {
}
public void addListener(EventListener listener) {
listeners.add(listener);
}
public void fireEventListeners(Event event) {
ArrayList<EventListener> listeners =
new ArrayList<EventListener>(this.listeners);
for (EventListener listener : listeners) {
listener.handleEvent(event);
}
}
public void removeListener(EventListener listener) {
listeners.remove(listener);
}
}
命令模式 vs 观察者模式
个人感觉(可能并没有深刻体会到两者的精髓,希望有经验的人可以指点一二),两者非常相似,观察者模式中其实就嵌套使用了命令模式。
在Subject主题调notifyObservers()方法通知各个观察者时,会遍历所有已注册的观察者对象Observer,调observer.update()方法来更新各个对象。
观察者模式里,主题Subject类的registerObserver(Observer o)和removeObserver(Observer o)方法对应了命令模式里的add()和remove()。
在观察者模式里,observer会有一个实例变量持有主题subject的引用,从而可以在observer类里调用subject.registerObserver()方法和subject.removeObserver()方法来向主题注册和注销自己。