设计模式实践-命令模式
2020-10-18 本文已影响0人
h2coder
什么是命令模式?什么时候用?
命令模式,可以将请求封装为一个命令对象,主要是以下场景:
- 需要支持取消操作
- 支持日志,在系统崩溃时,修改可以被重做一遍
- 需要支持事务操作
怎么实现命令模式?
命令模式涉及以下几个对象:
- Client,调用方对象
- Receiver,命令执行者,不一定得自己新建类,能支持命令功能的类即可
- Command,命令接口或抽象类,定义命令执行和撤销方法
- ConcreteCommand,具体命令类,持有Receiver命令接收者引用
- Invoker,命令请求者,持有一系列的Command实例,一般是提供撤销和反撤销功能
命令模式简单演示
- Receiver,命令执行者,这里简单演示一下,只打印字符串
public class Receiver {
/**
* 执行操作
*/
public void action() {
System.out.println("执行一个命令");
}
/**
* 撤销操作
*/
public void unAction() {
System.out.println("撤销一个命令");
}
}
- Command,命令接口,定义操作方法和撤销方法
public interface Command {
/**
* 执行操作
*/
void execute();
/**
* 撤销
*/
void undo();
}
- 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();
}
}
- 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();
}
}
- 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();
}
}
- 输出
执行一个命令
撤销一个命令
命令模式实践-画板
命令模式的使用场景,一般是用在需要撤销功能的场景,开发中一般比较少,不过还是能找到一些例子,例如实现一个画板功能,切换画笔颜色、笔触、撤销、反撤销等。
画板示例.png实现步骤
- 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();
}
- IDrawCommand,命令接口,提供绘制方法和撤销方法
public interface IDrawCommand {
/**
* 绘制操作
*
* @param canvas 画布
*/
void draw(Canvas canvas);
/**
* 撤销操作
*/
void undo();
}
- 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;
}
}
- 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();
}
}
- 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);
}
- 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) {
}
}
- 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) {
}
}
- 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();
}
}
}
- 具体使用(画板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,代码很多,并且还需要支持撤销功能时,可以使用命令模式拆分逻辑,不同的操作对应一个命令,增强代码可读性。