JAVA SCRIPT设计模式是本人根据GOF的设计模式写的博客记录。使用JAVA SCRIPT语言来实现主体功能,所以不可能像C++,JAVA等面向对象语言一样严谨,大部分程序都附上了JAVA SCRIPT代码,代码只是实现了设计模式的主体功能,不代表全部的正确,特此声明。若读者需要了解设计模式目录、原则、设计变化方向,环境相关等信息请查看设计模式开篇。
一、UML类图
参与者:
1.1 Command
- 声明执行操作的接口。
1.2 ConcreteCommand(PasteCommand,OpenCommand)
- 将一个接收者对象绑定于一个动作。
- 调用接收者相应的操作,以实现Execute。
- 如果可处理该请求,就处理之;否则将该请求转发给它的后继者。
1.3 Client
- 创建一个具体命令对象并设定它的接收者。
1.4 Invoker(MenuItem)
- 要求该命令执行这个请求。
1.5 Receiver(Document,Application)
- 知道如何实施与执行一个请求相关的操作。任何类都可能作为一个接收者。
二、意图
将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤消的操作。
三、适用性
- 像MenuItem对象那样,抽象出待执行的动作以参数化某对象。你可用过程语言中的回调(callback)函数表达这种参数化机制。所谓回调函数是指函数先在某处
注册,而它将在稍后某个需要的时候被调用。Command模式是回调机制的一个面向对象的替代品。 - 在不同的时刻指定、排列和执行请求。一个Command对象可以有一个与初始请求无关的生存期。如果一个请求的接收者可用一种与地址空间无关的方式表达,那么就可将负责该请求的命令对象传送给另一个不同的进程并在那儿实现该请求。
- 支持取消操作。Command的Excute操作可在实施操作前将状态存储起来,在取消操作时这个状态用来消除该操作的影响。Command接口必须添加一个Unexecute操作,该操作取消上一次Execute调用的效果。执行的命令被存储在一个历史列表中。可通过向后和向前遍历这一列表并分别调用Unexecute和Execute来实现重数不限的“取消”和“重做”。
- 支持修改日志,这样当系统崩溃时,这些修改可以被重做一遍。在Command接口中添加装载操作和存储操作,可以用来保持变动的一个一致的修改日志。从崩溃中恢复的过程包括从磁盘中重新读入记录下来的命令并用Execute操作重新执行它们。
- 用构建在原语操作上的高层操作构造一个系统。这样一种结构在支持事务(transaction)的信息系统中很常见。一个事务封装了对数据的一组变动。Command模式提供了对事务进行建模的方法。Command有一个公共的接口,使得你可以用同一种方式调用所有的事务。同时使用该模式也易于添加新事务以扩展系统。
四、示例代码
4.1 动机
有时必须向某对象提交请求,但并不知道关于被请求的操作或请求的接受者的任何信息。例如,用户界面工具箱包括按钮和菜单这样的对象,它们执行请求响应用户输入。但工具箱 不能显式的在按钮或菜单中实现该请求,因为只有使用工具箱的应用知道该由哪个对象做哪 个操作。而工具箱的设计者无法知道请求的接受者或执行的操作。
命令模式通过将请求本身变成一个对象来使工具箱对象可向未指定的应用对象提出请求。这个对象可被存储并像其他的对象一样被传递。这一模式的关键是一个抽象的Command类,它定义了一个执行操作的接口。其最简单的形式是一个抽象的Execute操作。具体的Command子类将接收者作为其一个实例变量,并实现Execute操作,指定接收者采取的动作。而接收者有执行该请求所需的具体信息。
用Command对象可很容易的实现菜单(Menu),每一菜单中的选项都是一个菜单项(MenuItem)类的实例。一个Application类创建这些菜单和它们的菜单项以及其余的用户界面。该Application类还跟踪用户已打开的Document对象。该应用为每一个菜单项配置一个具体的Command子类的实例。当用户选择了一个菜单项时,该MenuItem对象调用它的Command对象的Execute方法,而Execute执行相应操作。MenuItem对象并不知道它们使用的是Command的哪一个子类。Command子类里存放着请求的接收者,而Excute操作将调用该接收者的一个或多个操作。
4.2 示例UML
目录结构:
4.1 Command
- 声明执行操作的接口。
export default class Command {
state;
constructor( ) {
}
Execute() {
}
}
4.2 ConcreteCommand(PasteCommand,OpenCommand)
- 将一个接收者对象绑定于一个动作。
- 调用接收者相应的操作,以实现Execute。
- 如果可处理该请求,就处理之;否则将该请求转发给它的后继者。
import Command from '../Command.js';
import WordDocument from '../../Document/impl/WordDocument.js';
export default class OpenCommand extends Command {
application
constructor(application ) {
super();
this.application=application;
}
Execute() {
console.log(` ask user select file `);
let doc=new WordDocument();
this.application.Add(doc);
doc.Open();
}
}
import Command from '../Command.js';
export default class PasteCommand extends Command {
application
constructor(application ) {
super();
this.application=application;
}
Execute() {
this.application.doc.Paste();
}
}
4.3 Client/Application
- 创建一个具体命令对象并设定它的接收者。
import Menu from './Menu/Menu.js';
import MenuItem from './Menu/MenuItem.js';
import OpenCommand from './Command/impl/OpenCommand.js';
import PasteCommand from './Command/impl/PasteCommand.js';
import MyDocument from './Document/impl/MyDocument.js';
export default class Application{
menu;
doc;
zooRect;
constructor(ctx,zooRect) {
this.ctx=ctx;
this.zooRect=zooRect;
this.doc=new MyDocument();
this.createMenu(ctx);
this.Draw();
}
Add(document)
{
this.doc=document;
}
Draw()
{
this.ctx.clearRect(this.zooRect.startx,this.zooRect.starty,this.zooRect.width,this.zooRect.height);
this.menu.Draw();
}
onmousedown(e)
{
this.menu.onmousedown(e);
this.Draw();
}
onmousemove(e)
{
this.menu.onmousemove(e);
this.Draw();
}
createMenu(ctx)
{
this.menu=new Menu(ctx);
let fileItem=new MenuItem(ctx,'文件(F)',null);
this.menu.Add(fileItem);
let newItem=new MenuItem(ctx,'新建(N)',null);
fileItem.Add(newItem);
let openCommand=new OpenCommand(this);
let openItem=new MenuItem(ctx,'打开(O)',openCommand);
fileItem.Add(openItem);
let pasteCommand=new PasteCommand(this);
let pasteItem=new MenuItem(ctx,'粘贴(P)',pasteCommand);
fileItem.Add(pasteItem);
let editItem=new MenuItem(ctx,'编辑(E)',null);
this.menu.Add(editItem);
let helpItem=new MenuItem(ctx,'帮助(H)',null);
this.menu.Add(helpItem);
}
}
4.4 Invoker(MenuItem)
- 要求该命令执行这个请求。
export default class MenuItem {
ctx;
name;
command;
isleaf=true;
isclicked=false;
isMouseIn=false;
menuItemChildrens=[];
menuZooChildrens=[];
menuItemZoo={startx:10,starty:50,width:0,height:40};
constructor(ctx,name,command) {
this.ctx=ctx;
this.name=name;
this.command=command;
}
Draw(startx,starty,width,height)
{
this.menuItemZoo={startx:startx,starty:starty,width:width,height:height};
this.ctx.strokeRect(startx,starty,width,height);
if(this.isMouseIn){
this.ctx.save();
this.ctx.fillStyle="red";
this.ctx.fillRect(startx,starty,width,height);
this.ctx.restore();
}
this.ctx.textAlign="center";
this.ctx.fillText(this.name,startx+width/2,starty+3*height/4);
if(this.isclicked)
this.DrawItem()
}
DrawItem( )
{
this.menuZooChildrens=[];
if(!this.isleaf)
{
let x=this.menuItemZoo.startx;
let y=this.menuItemZoo.starty;
this.ctx.font="20px Arial";
for(let n=0;n<this.menuItemChildrens.length;n++)
{
let menuItem=this.menuItemChildrens[n];
menuItem.Draw(x,y+this.menuItemZoo.height,150,this.menuItemZoo.height);
let zooChild={startx:x,starty:y+this.menuItemZoo.height,width:150,height:this.menuItemZoo.height};
this.menuZooChildrens.push(zooChild);
y=y+this.menuItemZoo.height;
}
this.menuItemZoo.height= y;
}
}
Add(menuItem){
this.isleaf=false;
this.menuItemChildrens.push(menuItem);
}
Clicked()
{
if(this.isleaf&&this.command!=null)
this.command.Execute();
}
SetIsClicked(isClicked)
{
this.isclicked=isClicked;
if(isClicked)
{
this.Clicked();
}
}
SetIsMouseIn(isMouseIn)
{
this.isMouseIn=isMouseIn;
}
onmousedown(e)
{
let selectN=this.GetActiveItem(e);
if(selectN>-1)
{
for(let n=0;n<this.menuItemChildrens.length;n++)
{
let menuItem=this.menuItemChildrens[n];
if(n==selectN)
menuItem.SetIsClicked(true);
else
menuItem.SetIsClicked(false);
}
// this.Draw()
}
}
onmousemove(e)
{
let selectN=this.GetActiveItem(e);
if(selectN>-1)
{
for(let n=0;n<this.menuItemChildrens.length;n++)
{
let menuItem=this.menuItemChildrens[n];
if(n==selectN)
menuItem.SetIsMouseIn(true);
else
menuItem.SetIsMouseIn(false);
}
}
}
GetActiveItem(e)
{
var x = e.clientX;
var y = e.clientY;
if(x>this.menuItemZoo.startx&&x<this.menuItemZoo.startx+this.menuItemZoo.width
&&y>this.menuItemZoo.starty&&y<this.menuItemZoo.starty+this.menuItemZoo.height )
{
for(let n=0;n<this.menuZooChildrens.length;n++)
{
let zooChild=this.menuZooChildrens[n];
if(x>zooChild.startx&&x<zooChild.startx+zooChild.width
&&y>zooChild.starty&&y<zooChild.starty+zooChild.height )
{
return n;
}
}
}
else
return -1;
}
}
4.5 Receiver(Document)
- 知道如何实施与执行一个请求相关的操作。任何类都可能作为一个接收者。
import Document from '../Document.js';
export default class MyDocument extends Document {
constructor() {
super();
}
Open() {
console.log(` MyDocument Open `);
}
Close() {
console.log(` MyDocument Close `);
}
Save() {
console.log(` MyDocument Save `);
}
Revert() {
console.log(` MyDocument Revert `);
}
Paste(){
console.log(` MyDocument Paste `);
}
}
4.6 测试HTML
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script type="module">
import Application from './Application.js';
var canvas = document.getElementById("mycanvas")
var ctx = canvas.getContext("2d") //create 2d object
let cl = new Application(ctx,{startx:0,starty:0,width:900,height:900});
canvas.onmousedown = (e) => {
cl.onmousedown(e);
};
canvas.onmousemove = (e) => {
cl.onmousemove(e);
};
</script>
</head>
<body>
<canvas id="mycanvas" width=900px height=900px></canvas>
</body>
</html>
测试结果:
MyDocument.js:20 MyDocument Paste
OpenCommand.js:12 ask user select file
WordDocument.js:8 WordDocument Open
WordDocument.js:20 WordDocument Paste
五、源代码下载
下载链接:https://pan.baidu.com/s/1XuPqp84cccBNVkbnMY3sKw
提取码:q2ut