设计模式

设计模式实践-命令模式

2020-10-18  本文已影响0人  h2coder

什么是命令模式?什么时候用?

命令模式,可以将请求封装为一个命令对象,主要是以下场景:

  1. 需要支持取消操作
  2. 支持日志,在系统崩溃时,修改可以被重做一遍
  3. 需要支持事务操作

怎么实现命令模式?

命令模式涉及以下几个对象:

  1. Client,调用方对象
  2. Receiver,命令执行者,不一定得自己新建类,能支持命令功能的类即可
  3. Command,命令接口或抽象类,定义命令执行和撤销方法
  4. ConcreteCommand,具体命令类,持有Receiver命令接收者引用
  5. Invoker,命令请求者,持有一系列的Command实例,一般是提供撤销和反撤销功能

命令模式简单演示

  1. Receiver,命令执行者,这里简单演示一下,只打印字符串
public class Receiver {
    /**
     * 执行操作
     */
    public void action() {
        System.out.println("执行一个命令");
    }

    /**
     * 撤销操作
     */
    public void unAction() {
        System.out.println("撤销一个命令");
    }
}
  1. Command,命令接口,定义操作方法和撤销方法
public interface Command {
    /**
     * 执行操作
     */
    void execute();

    /**
     * 撤销
     */
    void undo();
}
  1. ConcreteCommand,具体命令类,持有命令执行者Receiver
public class ConcreteCommand implements Command {
    /**
     * 执行者,可以是具体的对象
     */
    private Receiver receiver;

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

    @Override
    public void execute() {
        receiver.action();
    }

    @Override
    public void undo() {
        receiver.unAction();
    }
}
  1. Invoker,命令请求者,持有命令对象
public class Invoker {
    private Command command;

    /**
     * 配置指令
     */
    public void setCommand(Command command) {
        this.command = command;
    }

    /**
     * 执行命令
     */
    public void executeCommand() {
        command.execute();
    }

    /**
     * 撤销命令
     */
    public void undoCommand() {
        command.undo();
    }
}
  1. Client调用方
public class Client {
    public static void main(String[] args) {
        Invoker invoker = new Invoker();
        ConcreteCommand command = new ConcreteCommand(new Receiver());
        invoker.setCommand(command);
        //执行命令
        invoker.executeCommand();
        //撤销命令
        invoker.undoCommand();
    }
}
  1. 输出
执行一个命令
撤销一个命令

命令模式实践-画板

命令模式的使用场景,一般是用在需要撤销功能的场景,开发中一般比较少,不过还是能找到一些例子,例如实现一个画板功能,切换画笔颜色、笔触、撤销、反撤销等。

画板示例.png

实现步骤

  1. DrawingBoardInterface,外部操作的API接口
public interface DrawingBoardInterface {
    /**
     * 增加一个命令
     *
     * @param command 命令
     */
    void addCommand(IDrawCommand command);

    /**
     * 撤销上一个命令
     */
    void undo();

    /**
     * 取消撤销
     */
    void redo();

    /**
     * 是否可以取消撤销
     *
     * @return true为可以取消撤销,false为不可以
     */
    boolean isCanRedo();

    /**
     * 是否可以撤销
     *
     * @return true为可以撤销,false为不可以
     */
    boolean isCanUndo();

    /**
     * 清空
     */
    void clean();
}
  1. IDrawCommand,命令接口,提供绘制方法和撤销方法
public interface IDrawCommand {
    /**
     * 绘制操作
     *
     * @param canvas 画布
     */
    void draw(Canvas canvas);

    /**
     * 撤销操作
     */
    void undo();
}
  1. DrawPathCommand,具体的绘制路径命令
public class DrawPathCommand implements IDrawCommand {
    /**
     * 本次命令的绘制路径
     */
    private Path mPath;
    /**
     * 绘制时使用的画笔
     */
    private Paint mPaint;

    @Override
    public void draw(Canvas canvas) {
        canvas.drawPath(mPath, mPaint);
    }

    @Override
    public void undo() {
    }

    public void setPath(Path path) {
        mPath = path;
    }

    public void setPaint(Paint paint) {
        mPaint = paint;
    }

    public Path getPath() {
        return mPath;
    }

    public Paint getPaint() {
        return mPaint;
    }
}
  1. DrawInvoker,保存应用中的命令和撤销的命令
public class DrawInvoker implements DrawingBoardInterface {
    /**
     * 会被绘制的路径命令
     */
    private List<IDrawCommand> mDrawingCommandList;
    /**
     * 取消的命令列表
     */
    private List<IDrawCommand> mRedoCommandList;

    public DrawInvoker() {
        mDrawingCommandList = new CopyOnWriteArrayList<>();
        mRedoCommandList = new CopyOnWriteArrayList<>();
    }

    /**
     * 增加一个命令
     */
    @Override
    public void addCommand(IDrawCommand command) {
        if (!mDrawingCommandList.contains(command)) {
            mDrawingCommandList.add(command);
        }
    }

    /**
     * 撤销上一个命令
     */
    @Override
    public void undo() {
        if (mDrawingCommandList.size() > 0) {
            //拿出最后一个命令进行撤销
            IDrawCommand command = mDrawingCommandList.get(mDrawingCommandList.size() - 1);
            mDrawingCommandList.remove(command);
            command.undo();
            mRedoCommandList.add(command);
        }
    }

    /**
     * 取消撤销
     */
    @Override
    public void redo() {
        if (mRedoCommandList.size() > 0) {
            IDrawCommand command = mRedoCommandList.get(mRedoCommandList.size() - 1);
            mRedoCommandList.remove(command);
            mDrawingCommandList.add(command);
        }
    }

    /**
     * 执行命令
     */
    public void execute(Canvas canvas) {
        for (IDrawCommand command : mDrawingCommandList) {
            command.draw(canvas);
        }
    }

    /**
     * 是否可以取消撤销
     */
    @Override
    public boolean isCanRedo() {
        return mRedoCommandList.size() > 0;
    }

    /**
     * 是否可以撤销
     */
    @Override
    public boolean isCanUndo() {
        return mDrawingCommandList.size() > 0;
    }

    @Override
    public void clean() {
        mRedoCommandList.addAll(mDrawingCommandList);
        mDrawingCommandList.clear();
    }
}
  1. IBrush,笔触接口,由于可以画直线,也可以画圆点,所以抽象出接口,不是命令模式里面需要涉及的类
public interface IBrush {
    /**
     * 触点接触时
     *
     * @param path 路径
     * @param x    按下的x坐标
     * @param y    按下的y坐标
     */
    void down(Path path, float x, float y);

    /**
     * 触点移动时
     *
     * @param path 路径
     * @param x    触点移动的x坐标
     * @param y    触点移动的y坐标
     */
    void move(Path path, float x, float y);

    /**
     * 触点离开时
     *
     * @param path 路径
     * @param x    触点离开时的x坐标
     * @param y    触点离开时的y坐标
     */
    void up(Path path, float x, float y);
}
  1. NormalBrush,直线笔触对象,实现IBrush接口
public class NormalBrush implements IBrush {

    @Override
    public void down(Path path, float x, float y) {
        path.moveTo(x, y);
    }

    @Override
    public void move(Path path, float x, float y) {
        path.lineTo(x, y);
    }

    @Override
    public void up(Path path, float x, float y) {
    }
}
  1. CircleBrush,圆形笔触对象,实现IBrush接口
public class CircleBrush implements IBrush {

    @Override
    public void down(Path path, float x, float y) {
    }

    @Override
    public void move(Path path, float x, float y) {
        path.addCircle(x, y, 10, Path.Direction.CW);
    }

    @Override
    public void up(Path path, float x, float y) {
    }
}
  1. DrawCanvas,继承于SufaceView的自定义控件,开启一个线程一直死循环,当添加绘制命令时,绘制笔触划过的路径。
public class DrawCanvas extends SurfaceView implements SurfaceHolder.Callback, DrawingBoardInterface {
    /**
     * 绘制执行者
     */
    private DrawInvoker mInvoker;
    /**
     * 绘制线程,是否正在运行
     */
    private boolean isRunning;
    /**
     * 是否可以绘制
     */
    private boolean isDrawing;
    /**
     * 绘制在Bitmap中
     */
    private Bitmap mPathBitmap;
    /**
     * 绘制线程
     */
    private DrawThread mDrawThread;

    public DrawCanvas(Context context) {
        super(context);
        init();
    }

    public DrawCanvas(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public DrawCanvas(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        mInvoker = new DrawInvoker();
        mDrawThread = new DrawThread();
        getHolder().addCallback(this);
    }

    @Override
    public void addCommand(IDrawCommand command) {
        mInvoker.addCommand(command);
        isDrawing = true;
    }

    @Override
    public void undo() {
        isDrawing = true;
        mInvoker.undo();
    }

    @Override
    public void redo() {
        isDrawing = true;
        mInvoker.redo();
    }

    @Override
    public boolean isCanRedo() {
        return mInvoker.isCanRedo();
    }

    @Override
    public boolean isCanUndo() {
        return mInvoker.isCanUndo();
    }

    @Override
    public void clean() {
        isDrawing = true;
        mInvoker.clean();
    }

    /**
     * 绘制线程
     */
    private class DrawThread extends Thread {
        @Override
        public void run() {
            super.run();
            Canvas canvas = null;
            while (isRunning) {
                if (isDrawing) {
                    try {
                        canvas = getHolder().lockCanvas(null);
                        if (mPathBitmap == null) {
                            mPathBitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
                        }
                        //绘制路径
                        Canvas pathCanvas = new Canvas(mPathBitmap);
                        pathCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
                        canvas.drawColor(0, PorterDuff.Mode.CLEAR);
                        mInvoker.execute(pathCanvas);
                        canvas.drawBitmap(mPathBitmap, 0, 0, null);
                    } finally {
                        getHolder().unlockCanvasAndPost(canvas);
                    }
                    isDrawing = false;
                }
            }
        }
    }

    //--------------------------- 生命周期回调 ---------------------------

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        isRunning = true;
        mDrawThread.start();
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        mPathBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        boolean retry = true;
        //销毁页面,将标志位设置为停止,停止线程
        isRunning = false;
        try {
            while (retry) {
                mDrawThread.join();
                retry = false;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
  1. 具体使用(画板View和其他操作按钮,布局就不贴了,逻辑也不难,就不贴了)
public class MainActivity extends AppCompatActivity {
    private DrawCanvas vDrawCanvas;
    private Button vChangeToRed;
    private Button vChangeToGreen;
    private Button vChangeToBlue;
    private Button vSwitchNormalPaint;
    private Button vSwitchCirclePaint;
    private Button vUndo;
    private Button vRedo;
    private Button vClean;

    private Paint mPaint;
    private IBrush mPaintBrush;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findView(findViewById(android.R.id.content));
        bindView();
        initPaint();
    }

    private void findView(View view) {
        vDrawCanvas = view.findViewById(R.id.draw_canvas);
        vChangeToRed = view.findViewById(R.id.change_to_red);
        vChangeToGreen = view.findViewById(R.id.change_to_green);
        vChangeToBlue = view.findViewById(R.id.change_to_blue);
        vSwitchNormalPaint = view.findViewById(R.id.switch_normal_paint);
        vSwitchCirclePaint = view.findViewById(R.id.switch_circle_paint);
        vUndo = view.findViewById(R.id.undo);
        vRedo = view.findViewById(R.id.redo);
        vClean = view.findViewById(R.id.clean);
    }

    @SuppressLint("ClickableViewAccessibility")
    private void bindView() {
        vChangeToRed.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                changePaintColor(Color.parseColor("#FF0000"));
            }
        });
        vChangeToGreen.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                changePaintColor(Color.parseColor("#FF00FF00"));
            }
        });
        vChangeToBlue.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                changePaintColor(Color.parseColor("#FF0000FF"));
            }
        });
        //切换笔触
        vSwitchNormalPaint.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                changePaintNormalBrush();
            }
        });
        vSwitchCirclePaint.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                changePaintCircleBrush();
            }
        });
        //撤销
        vUndo.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                vDrawCanvas.undo();
            }
        });
        //取消撤销
        vRedo.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                vDrawCanvas.redo();
            }
        });
        vClean.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                vDrawCanvas.clean();
            }
        });
        //添加手势,移动时改变路径的最终位置,松手时添加命令进行绘制
        vDrawCanvas.setOnTouchListener(new View.OnTouchListener() {
            /**
             * 绘制路径命令
             */
            private DrawPathCommand mPathCommand;

            @Override
            public boolean onTouch(View view, MotionEvent event) {
                int action = event.getAction();
                if (action == MotionEvent.ACTION_DOWN) {
                    mPathCommand = new DrawPathCommand();
                    mPathCommand.setPaint(mPaint);
                    mPathCommand.setPath(new Path());
                    mPaintBrush.down(mPathCommand.getPath(), event.getX(), event.getY());
                } else if (action == MotionEvent.ACTION_MOVE) {
                    mPaintBrush.move(mPathCommand.getPath(), event.getX(), event.getY());
                } else if (action == MotionEvent.ACTION_UP) {
                    mPaintBrush.up(mPathCommand.getPath(), event.getX(), event.getY());
                    vDrawCanvas.addCommand(mPathCommand);
                }
                return true;
            }
        });
    }

    private void initPaint() {
        //初始化画笔
        changePaintColor(Color.parseColor("#FFFFFF"));
        //初始化笔触
        changePaintNormalBrush();
    }

    /**
     * 改变画笔的颜色
     */
    private void changePaintColor(int color) {
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setColor(color);
        mPaint.setStrokeWidth(8);
        if (mPaintBrush instanceof NormalBrush) {
            changePaintNormalBrush();
        } else if (mPaintBrush instanceof CircleBrush) {
            changePaintCircleBrush();
        }
    }

    /**
     * 改变画笔的笔触为直线笔触
     */
    private void changePaintNormalBrush() {
        mPaintBrush = new NormalBrush();
        mPaint.setStyle(Paint.Style.STROKE);
    }

    /**
     * 改变画笔的笔触为圆形笔触
     */
    private void changePaintCircleBrush() {
        mPaintBrush = new CircleBrush();
        mPaint.setStyle(Paint.Style.FILL);
    }
}

总结

当一个操作的执行十分复杂,存在很多if-else,代码很多,并且还需要支持撤销功能时,可以使用命令模式拆分逻辑,不同的操作对应一个命令,增强代码可读性。

上一篇下一篇

猜你喜欢

热点阅读