定义
将一个请求封装成一个对象,从而让用户使用不同的请求把客户端参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。
理解
命令模式的本质是对命令进行封装,将 发出命令的责任 和 执行命令的责任 分割开。
请求的一方发出请求,要求执行一个操作;
接收的一方收到请求,并执行操作;
请求的一方不必知道接收请求一方的接口,更不必知道请求是怎么被接收、操作是否被执行、何时执行以及如何执行的;
请求本身也是一个对象,这个对象和其他对象一样可以被存储和传递。
模式结构
- 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处理一些自己的逻辑。
小结
- 命令模式将请求的发送与执行解耦,更弱的耦合性,更灵活的控制性以及更好的拓展性
- 可以在不同时刻指定、排列和执行请求
- 一个命令对象可以有与初始请求无关的生存期
- 可以支持取消、回退、前进等操作
- 支持修改日志功能,这样当面临系统崩溃时,这些修改可以被重做一遍
- 支持事物操作
命令模式基本上可以运用在任何地方,但是在实际开发中,需不需要或者说值不值得使用命令模式需要斟酌,因为这务必会涉及到大量类的创建