命令模式

定义

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

理解

命令模式的本质是对命令进行封装,将 发出命令的责任执行命令的责任 分割开。
请求的一方发出请求,要求执行一个操作;
接收的一方收到请求,并执行操作;
请求的一方不必知道接收请求一方的接口,更不必知道请求是怎么被接收、操作是否被执行、何时执行以及如何执行的;
请求本身也是一个对象,这个对象和其他对象一样可以被存储和传递。

模式结构

  • Command 抽象命令类

定义所以具体命令类的抽象接口

  • ConcreteCommand 具体命令类

    实现抽象命令类的接口,实现执行方法excute()来调用接收者对象的相应操作

  • Invoker 调用者

请求的发送者,它通过命令对象来执行请求,它与抽象命令类之间存在关联关系。在程序运行时,将调用具体命令对象的excute()方法,间接调用接收者的相关操作

  • Receiver 接收者

负责具体实现或实施一个请求,是执行具体逻辑的角色

  • Client 客户类

    在客户类中需要创建发送者对象和具体命令类对象,在创建具体命令对象时指定其对应的接收者,发送者和接收者之间通过具体命令对象实现间接调用

实现

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);
}

抽象笔触 抽象接收者 执行命令的抽象

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 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 interface IDraw {

  /**
   * 绘制命令
   *
   * @param canvas 画笔对象
   */
  void draw(Canvas canvas);

  /**
   * 撤销命令
   */
  void undo();
}

抽象命令类

public class DrawPath implements IDraw {

  public Path path;
  public Paint paint;

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

  @Override public void undo() {

  }
}

绘制路径的方法 具体命令类

public class DrawInvoker {

  /**
   * 绘制列表
   */
  private List<DrawPath> drawList = Collections.synchronizedList(new ArrayList<DrawPath>());

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

  /**
   * 增加一个命令  设值注入形式
   *
   * @parm command  具体命令类对象
   */
  public void add(DrawPath command) {
    redoList.clear();
    drawList.add(command);
  }

  /**
   * 撤销上一步的操作
   */
  public void undo() {
    if (drawList.size() > 0) {
      DrawPath undo = drawList.get(drawList.size() - 1);
      drawList.remove(drawList.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);
      drawList.add(redoCommand);
    }
  }

  /**
   * 执行命令
   * 业务方法  调用命令类的draw()方法
   */
  public void execute(Canvas canvas) {
    if (drawList != null) {
      for (DrawPath tmp : drawList) {
        tmp.draw(canvas);
      }
    }
  }

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

  /**
   * 判断是否可以撤销
   */
  public boolean canUndo() {
    return drawList.size() > 0;
  }
}

调用者类,对绘制命令的进一步封装,实现撤销和重做方法

public class DrawCanvas extends SurfaceView implements SurfaceHolder.Callback {

  public boolean isDrawing, isRunning;

  private Bitmap mBitmap;
  private DrawInvoker mInvoker;
  private DrawThread mDrawThread;

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

    mInvoker = new DrawInvoker();
    mDrawThread = new DrawThread();

    getHolder().addCallback(this);
  }

  @Override public void surfaceCreated(SurfaceHolder holder) {
    isRunning = true;
    mDrawThread.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 {
        mDrawThread.join();
        retry = false;
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
  }

  /**
   * 增加绘制路径
   */
  public void add(DrawPath path) {
    mInvoker.add(path);
  }

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

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

  public boolean canRedo() {
    return mInvoker.canRedo();
  }

  public boolean canUndo() {
    return mInvoker.canUndo();
  }

  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;
        }
      }
    }
  }

  @Override public boolean performClick() {
    return super.performClick();
  }
}

自定义view ,承担画板功能,真正的执行者画布

public class DrawActivity extends AppCompatActivity implements View.OnClickListener {

    private DrawPath mPath;
    private DrawCanvas mCanvas;
    private Paint mPaint;
    private IBrush mBrush;

    private Button btnRedo, btnUndo;

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

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

        mBrush = new NormalBrush();

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

        btnRedo = findViewById(R.id.redo);
        btnUndo = findViewById(R.id.undo);
        btnRedo.setEnabled(false);
        btnUndo.setEnabled(false);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.red:
                mPaint = new Paint();
                mPaint.setColor(0xFFFF0000);
                mPaint.setStrokeWidth(3);
                break;
            case R.id.green:
                mPaint = new Paint();
                mPaint.setColor(0xFF00FF00);
                mPaint.setStrokeWidth(3);
                break;
            case R.id.blue:
                mPaint = new Paint();
                mPaint.setColor(0xFF0000FF);
                mPaint.setStrokeWidth(3);
                break;
            case R.id.normal:
                mBrush = new NormalBrush();
                break;
            case R.id.circle:
                mBrush = new CircleBrush();
                break;
            case R.id.redo:
                mCanvas.redo();
                if (!mCanvas.canRedo()) {
                    btnRedo.setEnabled(false);
                }
                btnUndo.setEnabled(true);
                break;
            case R.id.undo:
                mCanvas.undo();
                if (!mCanvas.canUndo()) {
                    btnUndo.setEnabled(false);
                }
                btnRedo.setEnabled(true);
                break;
            default:
                break;
        }
    }

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

测试Activity,Client类

在ACTION_DOWN、ACTION_UP事件触发时,mBrush会执行对应的事件方法,而不用关心具体的画笔是什么样子的。如果要增加一个新的样式也只需要添加一个继承IBrush接口的具体接收者对象,修改画笔样式只需要将mBrush指向对应的具体画笔类

诸如撤销,重做,或者日志保存等等之类的操作,交由DrawCanvas去实现,Client类只需要调用对应的方法,而不需关系具体实现逻辑,在DrawCanvas中则关联了DrawInvoker对象,实现撤销和重做等方法正式有DrawInvoker正式实现的。

Client在这里只做了这件事情,1发出执行请求,2给定接收对象,3处理一些自己的逻辑。

小结

  • 命令模式将请求的发送与执行解耦,更弱的耦合性,更灵活的控制性以及更好的拓展性
  • 可以在不同时刻指定、排列和执行请求
  • 一个命令对象可以有与初始请求无关的生存期
  • 可以支持取消、回退、前进等操作
  • 支持修改日志功能,这样当面临系统崩溃时,这些修改可以被重做一遍
  • 支持事物操作

命令模式基本上可以运用在任何地方,但是在实际开发中,需不需要或者说值不值得使用命令模式需要斟酌,因为这务必会涉及到大量类的创建

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容