设计模式实战命令模式:原理篇

Posted mo_weifeng

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了设计模式实战命令模式:原理篇相关的知识,希望对你有一定的参考价值。

前言

小明在公司负责开发音乐应用,用户听音乐的时候可以点击播放、暂停、上一首、下一首,可以循环播放。

小明是这样写的:

/**
 * 循环播放的音乐播放器实例
 */
public class PlayerManager 

    private List<String> mList = new ArrayList();
    private int pos = 0;

    public PlayerManager() 

    

    public int getPos() 
        return pos;
    

    public List<String> getList() 
        return mList;
    

    public void setList(List<String> list) 
        mList = list;
    

    public void start() 
        System.out.println("开始播放:" + mList.get(pos));
    

    public void stop() 
        System.out.println("暂停播放:" + mList.get(pos));
    

    public void next() 
        System.out.println("播放下一首==============================>");
        if (mList.size() == (pos + 1)) 
            pos = 0;
         else 
            pos++;
        
        start();
    

    public void pre() 
        System.out.println("播放上一首==============================>");
        if (pos == 0) 
            pos = mList.size() - 1;
         else 
            pos--;
        
        start();
    


public void test() 
        PlayerManager player = new PlayerManager();
        List<String> mList = new ArrayList();
        mList.add("1.mp3");
        mList.add("2.mp3");
        mList.add("3.mp3");
        player.setList(mList);

        player.start();
        player.stop();

        player.next();
        player.next();
        player.next();
        player.next();

        player.pre();
        player.pre();
        player.pre();
        player.pre();
    

输出结果

开始播放:1.mp3
暂停播放:1.mp3
播放下一首==============================>
开始播放:2.mp3
播放下一首==============================>
开始播放:3.mp3
播放下一首==============================>
开始播放:1.mp3
播放下一首==============================>
开始播放:2.mp3
播放上一首==============================>
开始播放:1.mp3
播放上一首==============================>
开始播放:3.mp3
播放上一首==============================>
开始播放:2.mp3
播放上一首==============================>
开始播放:1.mp3

小明的领导一看,说这样写不行,如果业务逻辑更复杂了,比如增加顺序播放、随机播放、单首循环播放,或者在播放前要判断是否会员,是否购买该歌曲,岂不是要修改一直Player类?Player类越来越臃肿。Player应该只负责播放器的功能,不应该掺杂业务逻辑,不符合单一职责。Player的扩展性也很低,如果要增加新的播放器命令,比如音效、倍速播放、定时关闭等等功能,只能在原来的基础上去增加。

领导建议使用命令模式,播放器负责播放逻辑,用户负责请求,命令负责执行逻辑。


使用命令模式改造

改造时,增加一个业务逻辑:当播放某首没有权限的歌时,自动切换到下一首歌。

播放器只负责播放器的功能,比如播放和暂停。

/**
 * 音乐播放器实例
 */
public class Player 

    public Player() 

    

    public void start(String url) 
        System.out.println("开始播放:" + url);
    

    public void stop(String url ) 
        System.out.println("暂停播放:" + url);
    


抽象出命令类

public interface Command 
    /**
     * 命令执行方法
     */
    void execute();

具体命令

public class StartCommand implements Command 
    private Player mPlayer;

    public StartCommand(Player player) 
        mPlayer = player;
    

    @Override
    public void execute(String url) 
        mPlayer.start(url);
    


public class StopCommand implements Command 
    private Player mPlayer;

    public StopCommand(Player player) 
        mPlayer = player;
    

    @Override
    public void execute(String url) 
        mPlayer.stop(url);
    


Buttons通过命令对播放器进行控制,写一些业务逻辑

public class Buttons 
    private List<String> mList = new ArrayList();
    private int pos = 0;
    private StartCommand mStartCommand;
    private StopCommand mStopCommand;

    public void setStartCommand(StartCommand startCommand) 
        mStartCommand = startCommand;
    

    public void setStopCommand(StopCommand stopCommand) 
        mStopCommand = stopCommand;
    

    public void start() 

        String url = mList.get(pos);
        //假设3.map没有权限播放
        if (url.equals("3.mp3")) 
            System.out.println("没有“3.mp3”的播放权限,将播放下一首歌");
            //发送播放下一首歌的指令
            next();
            return;
        
        mStartCommand.execute(mList.get(pos));
    

    public void stop() 
        mStopCommand.execute(mList.get(pos));
    

    public void next() 
        pos = getNextUrlPos();
        start();
    

    public void pre() 
        pos = getPreUrlPos();
        start();
    

    public List<String> getList() 
        return mList;
    

    public void setList(List<String> list) 
        mList = list;
    

    public int getNextUrlPos() 
        int next;
        if (mList.size() == (pos + 1)) 
            next = 0;
         else 
            next = pos + 1;
        
        return next;
    

    public int getPreUrlPos() 
        int next;
        if (pos == 0) 
            next = mList.size() - 1;
         else 
            next = pos - 1;
        
        return next;
    


开始测试

 public void test() 
         Player player = new Player();

        StartCommand startCommand = new StartCommand(player);
        StopCommand stopCommand = new StopCommand(player);

        Buttons buttons = new Buttons();
        buttons.setStartCommand(startCommand);
        buttons.setStopCommand(stopCommand);

        List<String> mList = new ArrayList();
        mList.add("1.mp3");
        mList.add("2.mp3");
        mList.add("3.mp3");
        buttons.setList(mList);

        buttons.start();
        buttons.stop();

        buttons.next();
        buttons.next();
        buttons.next();

        buttons.pre();
        buttons.pre();
        buttons.pre();

    

输出结果

开始播放:1.mp3
暂停播放:1.mp3
开始播放:2.mp3
没有“3.mp3”的播放权限,将播放下一首歌
开始播放:1.mp3
开始播放:2.mp3
开始播放:1.mp3
没有“3.mp3”的播放权限,将播放下一首歌
开始播放:1.mp3
没有“3.mp3”的播放权限,将播放下一首歌
开始播放:1.mp3

可以看到,播放器类只专注于自己的播放和暂停功能即可。Buttons不直接调用播放器的方法,而是通过具体命令去调用,这样无论播放器怎么改动,只会影响命令,不会影响Buttons。Buttons负责具体的业务逻辑,如果有一天,需要另外做一套有不同逻辑的播放界面,那么只需要再另外创建一个Buttons,再写一套业务逻辑即可。如果不使用命令模式,如果需要改动业务逻辑,岂不是要在Player上修改?Player将逻辑越来越复杂,让人难以理解。

命令模式讲解

看一下命令模式的UML图

Receiver,接收者:执行具体逻辑的底层代码,任何一个类都可以成为一个接收者,封装了具体操作逻辑的方法称为行动方法。

Command,抽象命令:定义所有具体命令类的抽象接口。

ConcreteCommand,具体命令:实现了Command接口,负责执行接收者的方法。

Invoker,请求者:负责调用具体命令

意图:
将一个请求封装成一个对象,从而使您可以用不同的请求对客户进行参数化。

主要解决:
在软件系统中,行为请求者与行为实现者通常是一种紧耦合的关系,但某些场合,比如需要对行为进行记录、撤销或重做、事务等处理时,这种无法抵御变化的紧耦合的设计就不太合适。

命令模式进阶

假设我们想一次性执行多个命令呢?

public class Controller 
    private List<Command> Commands = new ArrayList<>();

    public void addCommand(Command Command) 
        Commands.add(Command);
    

    public void execute(Command Command) 
        Command.execute();
    

    public void executes() 
        for (Command Command:
             Commands) 
            Command.execute();
        
        Commands.clear();
    


        // 控制条可以执行单挑命令也可以批量执行多条命令
        VideoPlayer player = new VideoPlayer();
        Controller controller = new Controller();
        controller.execute(new PlayCommand(player));

        controller.addCommand(new PauseCommand(player));
        controller.addCommand(new PlayCommand(player));
        controller.addCommand(new StopCommand(player));
        controller.addCommand(new SpeedCommand(player));
        controller.executes();

假设我们想对命令做一个撤销功能呢?比如键盘打字后,按“Ctrl+Z”可以返回上一个操作

ICommand命令类

public interface ICommand 

    void write();

    void undo();


WriteCommand具体命令类

public class WriteCommand implements ICommand 

    public String word;
    public Tablet tablet;

    public WriteCommand( Tablet tablet,String word) 
        this.word = word;
        this.tablet = tablet;
    

    @Override
    public void write() 
        tablet.write(word);
    

    @Override
    public void undo() 
        tablet.undo();
    
    

接收者Tablet ,负责具体的写字和撤销逻辑


/**
 * 写字板实例
 */
public class Tablet 
    private List<String> mList = new ArrayList<>();

    public void write(String word) 
        mList.add(word);
        System.out.println("显示:" + mList.toString());
    

    public void undo() 
        if (mList.size() > 0) 
            mList.remove(mList.size() - 1);
        
        System.out.println("显示:" + mList.toString());
    


调用者Invoker类,将执行过的命令的都保存起来,方便撤销


public class Invoker 

    private List<WriteCommand> mCommands = new ArrayList<>();

    public void execute(WriteCommand command) 
        mCommands.add(command);
        command.write();
    

    public void executes(List<WriteCommand> commands) 
        mCommands.addAll(commands);
        for (ICommand command :
                commands) 
            command.write();
        
    

    public void undo() 
        if (mCommands.size() > 0) 
            WriteCommand writeCommand = mCommands.get(mCommands.size() - 1);
            writeCommand.undo();
        
    


测试

  public void test5() 
			 Tablet tablet = new Tablet();
        
        Invoker invoker = new Invoker();
        invoker.execute(new WriteCommand设计模式实战命令模式:原理篇

设计模式实战策略模式:原理篇

设计模式实战策略模式:原理篇

设计模式实战策略模式:原理篇

设计模式实战状态模式:原理篇

设计模式实战状态模式:原理篇