美文网首页设计模式
设计模式实践-命令模式

设计模式实践-命令模式

作者: h2coder | 来源:发表于2020-10-18 00:56 被阅读0次

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

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

    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,代码很多,并且还需要支持撤销功能时,可以使用命令模式拆分逻辑,不同的操作对应一个命令,增强代码可读性。

    相关文章

      网友评论

        本文标题:设计模式实践-命令模式

        本文链接:https://www.haomeiwen.com/subject/ghompktx.html