Android开发项目踩坑Android进阶

让程序畅通执行——命令模式

2019-03-05  本文已影响4人  程序员丶星霖

《Android源码设计模式解析与实战》读书笔记(十一)
《Android源码设计模式解析与实战》PDF资料下载

一、命令模式简介

命令模式,是行为型设计模式。

1.1、定义

将一个请求封装成一个对象,从而让用户使用不同的请求把客户端参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。

1.2、使用场景

  1. 需要抽象出待执行的动作,然后以参数的形式提供出来——类似于过程设计中的回调机制,而命令模式正是回调机制的一个面向对象的替代品。
  2. 在不同的时刻指定、排列和执行请求。一个命令对象可以有与初始请求无关的生存期。
  3. 需要支持取消操作。
  4. 支持修改日志功能,这样当系统崩溃时,这些修改可以被重做一遍。
  5. 需要支持事务操作。

二、命令模式的简单实现

以俄罗斯方块游戏为例:

//接收者角色
public class TetrisMachine {
    /**
     * 真正处理"向左"操作的逻辑代码
     */
    public void toLeft(){
        System.out.println("向左");
    }

    /**
     * 真正处理"向右"操作的逻辑代码
     */
    public void toRight(){
        System.out.println("向右");
    }

    /**
     * 真正处理"快速落下"操作的逻辑代码
     */
    public void fastToBottom() {
        System.out.println("快速落下");
    }

    /**
     * 真正处理"改变形状"操作的逻辑代码
     */
    public void transform() {
        System.out.println("改变形状");
    }
}
//命令者抽象
public interface Command {
    /**
     * 命令执行方法
     */
    void execute();
}
//具体命令者,向左移的命令类
public class LeftCommand implements Command {
    //持有一个接收者对象的引用
    private TetrisMachine machine;

    public LeftCommand(TetrisMachine machine) {
        this.machine = machine;
    }

    @Override
    public void execute() {
        //调用游离的具体方法执行操作
        machine.toLeft();
    }
}
//具体命令类,向右移的命令类
public class RightCommand implements Command {
    //持有一个接收者对象的引用
    private TetrisMachine machine;

    public RightCommand(TetrisMachine machine) {
        this.machine = machine;
    }

    @Override
    public void execute() {
        //调用游戏里的具体方法执行操作
        machine.toRight();
    }
}
//具体命令者,快速落下的命令类
public class FallCommand implements Command {
    //持有一个接收者对象的引用
    private TetrisMachine machine;

    public FallCommand(TetrisMachine machine) {
        this.machine = machine;
    }

    @Override
    public void execute() {
        machine.fastToBottom();
    }
}
//具体命令者,改变形状的命令类
public class TransformCommand implements Command {
    private TetrisMachine machine;

    public TransformCommand(TetrisMachine machine) {
        this.machine = machine;
    }

    @Override
    public void execute() {
        machine.transform();
    }
}
//请求者类,命令由按钮发起
public class Buttons {
    private LeftCommand leftCommand;//向左移动的命令对象引用
    private RightCommand rightCommand;//向右移动的命令对象引用
    private FallCommand fallCommand;//快速落下的命令对象引用
    private TransformCommand transformCommand;//变换形状的命令对象引用

    /**
     * 设置向左移动的命令对象
     *
     * @param leftCommand
     */
    public void setLeftCommand(LeftCommand leftCommand) {
        this.leftCommand = leftCommand;
    }

    /**
     * 设置向右移动的命令对象
     *
     * @param rightCommand
     */
    public void setRightCommand(RightCommand rightCommand) {
        this.rightCommand = rightCommand;
    }

    /**
     * 设置快速落下的命令对象
     *
     * @param fallCommand
     */
    public void setFallCommand(FallCommand fallCommand) {
        this.fallCommand = fallCommand;
    }

    /**
     * 设置变换形状的命令对象
     *
     * @param transformCommand
     */
    public void setTransformCommand(TransformCommand transformCommand) {
        this.transformCommand = transformCommand;
    }

    /**
     * 按下按钮向左移动
     */
    public void toLeft() {
        leftCommand.execute();
    }

    /**
     * 按下按钮向右移动
     */
    public void toRight() {
        rightCommand.execute();
    }

    /**
     * 按下按钮快速落下
     */
    public void fall() {
        fallCommand.execute();
    }

    /**
     * 按下按钮改变形状
     */
    public void transform() {
        transformCommand.execute();
    }
}
public class Player {
    public static void main(String[] args) {
        //首先要有俄罗斯方块游戏
        TetrisMachine machine = new TetrisMachine();

        //根据游戏构造4中命令
        LeftCommand leftCommand = new LeftCommand(machine);
        RightCommand rightCommand = new RightCommand(machine);
        FallCommand fallCommand = new FallCommand(machine);
        TransformCommand transformCommand = new TransformCommand(machine);

        //按钮可以执行不同的命令
        Buttons buttons = new Buttons();
        buttons.setLeftCommand(leftCommand);
        buttons.setRightCommand(rightCommand);
        buttons.setFallCommand(fallCommand);
        buttons.setTransformCommand(transformCommand);

        //具体按下哪个按钮玩家说了算
        buttons.toLeft();
        buttons.toRight();
        buttons.fall();
        buttons.transform();
    }
}

输出结果:

命令模式.png

三、命令模式实战

//抽象笔触
public interface IBrush {

    /**
     * 触点接触时
     * @param path
     * @param x
     * @param y
     */
    void down(Path path, float x, float y);

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

    /**
     * 触点离开时
     * @param path
     * @param x
     * @param y
     */
    void up(Path path, float x, float y);
}
//普通笔触
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) {

    }
}
//圆形笔触
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) {

    }
}
//绘制命令接口
public interface IDraw {
    /**
     * 绘制命令
     * @param canvas
     */
    void draw(Canvas canvas);

    /**
     * 撤销命令
     */
    void undo();
}
//具体绘制路径命令
public class DrawPath implements IDraw {
    public Path path;//需要绘制的路径
    public Paint mPaint;//绘制画笔

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

    @Override
    public void undo() {

    }
}
//绘制请求封装类
public class DrawInvoker {
    //绘制列表
    private List<DrawPath> mDrawPathList = Collections.synchronizedList(new ArrayList<DrawPath>());

    //重做列表
    private List<DrawPath> redoList = Collections.synchronizedList(new ArrayList<DrawPath>());

    /**
     * 增加一个命令
     * @param command
     */
    public void add(DrawPath command) {
        redoList.clear();
        mDrawPathList.add(command);
    }

    /**
     * 撤销上一步的命令
     */
    public void undo() {
        if (mDrawPathList.size() > 0) {
            DrawPath undo = mDrawPathList.get(mDrawPathList.size() - 1);
            mDrawPathList.remove(mDrawPathList.size() - 1);
            undo.undo();
            redoList.add(undo);
        }
    }

    /**
     * 重做上一步撤销的命令
     */
    public void redo() {
        if (redoList.size() > 0) {
            DrawPath redoCommand = redoList.get(redoList.size() - 1);
            redoList.remove(redoList.size() - 1);
            mDrawPathList.add(redoCommand);
        }
    }

    /**
     * 执行命令
     * @param canvas
     */
    public void execute(Canvas canvas) {
        if (mDrawPathList != null) {
            for (DrawPath tmp : mDrawPathList) {
                tmp.draw(canvas);
            }
        }
    }

    /**
     * 是否可以重做
     * @return
     */
    public boolean canRedo() {
        return redoList.size() > 0;
    }

    /**
     * 是否可以撤销
     * @return
     */
    public boolean canUndo() {
        return mDrawPathList.size() > 0;
    }
}
//绘制的真正执行者画布
public class DrawCanvas extends SurfaceView implements SurfaceHolder.Callback {
    public boolean isDrawing, isRunning;//标识是否可以绘制、绘制线程是否可以运行

    private Bitmap mBitmap;//绘制到的位图对象
    private DrawInvoker mInvoker;//绘制命令请求对象
    private DrawThread mThread;//绘制线程

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

        mInvoker = new DrawInvoker();
        mThread = new DrawThread();
        getHolder().addCallback(this);
    }

    /**
     * 增减一条绘制路径
     *
     * @param path
     */
    public void add(DrawPath path) {
        mInvoker.add(path);
    }

    /**
     * 重做上一步撤销的绘制
     */
    public void redo() {
        isDrawing = true;
        mInvoker.redo();
    }

    /**
     * 撤销上一步的绘制
     */
    public void undo() {
        isDrawing = true;
        mInvoker.undo();
    }

    /**
     * 是否可以撤销
     *
     * @return
     */
    public boolean canUndo() {
        return mInvoker.canUndo();
    }


    /**
     * 是否可以重做
     *
     * @return
     */
    public boolean canRedo() {
        return mInvoker.canRedo();
    }

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

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

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        boolean retry = true;
        isRunning = false;
        while (retry) {
            try {
                mThread.join();
                retry = false;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    private class DrawThread extends Thread {
        @Override
        public void run() {
            Canvas canvas = null;
            while (isRunning) {
                if (isDrawing) {
                    try {
                        canvas = getHolder().lockCanvas(null);
                        if (mBitmap == null) {
                            mBitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
                        }
                        Canvas c = new Canvas(mBitmap);
                        c.drawColor(0, PorterDuff.Mode.CLEAR);
                        canvas.drawColor(0,PorterDuff.Mode.CLEAR);
                        mInvoker.execute(c);
                        canvas.drawBitmap(mBitmap, 0, 0, null);
                    }finally {
                        getHolder().unlockCanvasAndPost(canvas);
                    }
                    isDrawing = false;
                }
            }
        }
    }
}
public class DrawActivity extends AppCompatActivity implements View.OnClickListener {
    private DrawCanvas mCanvas;//绘制画布
    private DrawPath mPath;//路径绘制命令
    private Paint mPaint;//画笔对象
    private IBrush mBrush;//笔触对象

    private Button btnRedo, btnUndo;//重做、撤销按钮

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.ac_draw_color_red_btn://切换为红色
                mPaint = new Paint();
                mPaint.setStrokeWidth(3);
                mPaint.setColor(0xFFFF0000);
                break;
            case R.id.ac_draw_color_green_btn://切换为绿色
                mPaint = new Paint();
                mPaint.setStrokeWidth(3);
                mPaint.setColor(0xFF00FF00);
                break;
            case R.id.ac_draw_color_blue_btn://切换为蓝色
                mPaint = new Paint();
                mPaint.setStrokeWidth(3);
                mPaint.setColor(0xFF0000FF);
                break;
            case R.id.ac_draw_operate_undo_btn://撤销操作
                mCanvas.undo();
                if (!mCanvas.canUndo()) {
                    btnUndo.setEnabled(false);
                }
                btnRedo.setEnabled(true);
                break;
            case R.id.ac_draw_operate_redo_btn://重做操作
                mCanvas.redo();
                if (!mCanvas.canRedo()) {
                    btnRedo.setEnabled(false);
                }
                btnUndo.setEnabled(true);
                break;
            case R.id.ac_draw_brush_circle_btn://切换为圆形笔触
                mBrush = new CircleBrush();
                break;
            case R.id.ac_draw_brush_normal_btn://切换为正常笔触
                mBrush = new NormalBrush();
                break;
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_draw);

        mPaint = new Paint();
        mPaint.setColor(0xFFFFFFFF);
        mPaint.setStrokeWidth(3);

        mBrush = new NormalBrush();

        mCanvas = (DrawCanvas) findViewById(R.id.ac_draw_canvas);
        mCanvas.setOnTouchListener(new DrawTouchListener());

        btnRedo = (Button) findViewById(R.id.ac_draw_operate_redo_btn);
        btnRedo.setEnabled(false);
        btnUndo = (Button) findViewById(R.id.ac_draw_operate_undo_btn);
        btnUndo.setEnabled(false);
        btnUndo.setOnClickListener(this);
        btnRedo.setOnClickListener(this);
        findViewById(R.id.ac_draw_color_red_btn).setOnClickListener(this);
        findViewById(R.id.ac_draw_color_green_btn).setOnClickListener(this);
        findViewById(R.id.ac_draw_color_blue_btn).setOnClickListener(this);
        findViewById(R.id.ac_draw_brush_normal_btn).setOnClickListener(this);
        findViewById(R.id.ac_draw_brush_circle_btn).setOnClickListener(this);
    }

    private class DrawTouchListener implements View.OnTouchListener {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            if (event.getAction() == MotionEvent.ACTION_DOWN) {
                mPath = new DrawPath();
                mPath.mPaint = mPaint;
                mPath.path = new Path();
                mBrush.down(mPath.path, event.getX(), event.getY());
            } else if (event.getAction() == MotionEvent.ACTION_MOVE) {
                mBrush.move(mPath.path, event.getX(), event.getY());
            } else if (event.getAction() == MotionEvent.ACTION_UP) {
                mBrush.up(mPath.path, event.getX(), event.getY());
                mCanvas.add(mPath);
                mCanvas.isDrawing = true;
            }
            btnUndo.setEnabled(true);
            btnRedo.setEnabled(false);
            return true;
        }
    }

}

布局如下:

命令模式布局.png

四、总结

4.1、优点

更弱的耦合性、更灵活的控制性以及更好的扩展性。

4.2、缺点

类的膨胀,大量衍生类的创建。

学海无涯苦作舟

我的微信公众号
上一篇 下一篇

猜你喜欢

热点阅读