JAVA SCRIPT设计模式--行为型--设计模式之Memnto备忘录模式(18)

         JAVA SCRIPT设计模式是本人根据GOF的设计模式写的博客记录。使用JAVA SCRIPT语言来实现主体功能,所以不可能像C++,JAVA等面向对象语言一样严谨,大部分程序都附上了JAVA SCRIPT代码,代码只是实现了设计模式的主体功能,不代表全部的正确,特此声明。若读者需要了解设原则、设计变化方向,环境相关等信息请查看设计模式开篇

        所有JAVA SCRIPT设计模式快捷连接:

              创建型:(1)  抽象工厂 (2) 生成器 (3) 工厂方法 (4) 原型  (5) 单例

              结构型:(6) 适配器  (7) 桥接  (8) 组合 (9) 装饰 (10) 外观 (11) 享元 (12) 代理​

              行为型:(13) ​职责链 (14) ​命令 (15) ​解释器 (16) ​迭代器 (17) ​中介者 (18) ​备忘录 (119) ​观察者 (20) ​状态​ (21) ​策略 (22) ​模板方法 (23) 访问者​


一、UML类图

参与者:

1.1 Memento(备忘录,如SolverState)

  • 备忘录存储原发器对象的内部状态。原发器根据需要决定备忘录存储原发器的哪些内部状态。
  • 防止原发器以外的其他对象访问备忘录。备忘录实际上有两个接口,管理者(caretaker)只能看到备忘录的窄接口—它只能将备忘录传递给其他对象。相反,原发器能够看到一个宽接口,允许它访问返回到先前状态所需的所有数据。理想的情况是只允许生成本备忘录的那个原发器访问本备忘录的内部状态。

1.2 Originator(原发器,如ConstraintSolver)

  • 原发器创建一个备忘录,用以记录当前时刻它的内部状态。
  • 使用备忘录恢复内部状态.。

1.3 Caretaker(负责人,如undomechanism)

  • 负责保存好备忘录
  • 不能对备忘录的内容进行操作或检查。

1.4 协作

三、意图

     在不破坏封装性的前提下,捕获一个对象的内部状态并在该对象之外保存这个状态。 这样以后就可将该对象恢复到原先保存的状态

四、适用性 

  1. 必须保存一个对象在某一个时刻的 (部分)状态, 这样以后需要时它才能恢复到先前的状态。
  2. 如果一个用接口来让其它对象直接得到这些状态,将会暴露对象的实现细节并破坏对象的封装性。

五、示例代码

5.1  动机

        有时有必要记录一个对象的内部状态。为了允许用户取消不确定的操作或从错误中恢复过来,需要实现检查点和取消机制 , 而要实现这些机制,你必须事先将状态信息保存在某处, 这样才能将对象恢复到它们先前的状态。但是对象通常封装了其部分或所有的状态信息 , 使得其状态不能被其他对象访问,也就不可能在该对象之外保存其状态。而暴露其内部状态又将违反封装的原则,可能有损应用的可靠性和可扩展性。

        例如,考虑一个图形编辑器,它支持图形对象间的连线。用户可用一条直线连接两个矩 形, 而当用户移动任意一个矩形时 ,这两个矩形仍能保持连接。在移动过程中,编辑器自动伸展这条直线以保持该连接。

         一个众所周知的保持对象间连接关系的方法是使用一个约束解释系统。我们可将这一功能封装在一个ConstraintSolver对象中。ConstraintSolver在连接生成时,记录这些连接并产生
描述它们的数学方程。
当用户生成一个连接或修改图形时,ConstraintSolver就求解这些方程。并根据它的计算结果重新调整图形,使各个对象保持正确的连接。

         一个备忘录(memento)是一个对象,它存储另一个对象在某个瞬间的内部状态,而后者称为备忘录的原发器(originator)。当需要设置原发器的检查点时,取消操作机制会向原发器请求一个备忘录。原发器用描述当前状态的信息初始化该备忘录。只有原发器可以向备忘录中存取信息,备忘录对其他的对象“不可见”

        在刚才讨论的图形编辑器的例子中,ConstraintSolver可作为一个原发器。下面的事件序列描述了取消操作的过程:
             1)作为移动操作的一个副作用,编辑器向ConstraintSolver请求一个备忘录。
             2)ConstraintSolver创建并返回一个备忘录,在这个例子中该备忘录是SolverState类的一个实例。SolverState备忘录包含一些描述ConstraintSolver的内部等式和变量当前状态的数据结构。
             3)此后当用户取消移动操作时,编辑器将SolverState备忘录送回给ConstraintSolver。
             4)根据SolverState备忘录中的信息,ConstraintSolver改变它的内部结构以精确地将它的等式和变量返回到它们各自先前的状态。
        这一方案允许ConstraintSolver把恢复先前状态所需的信息交给其他的对象,而又不暴露它的内部结构和表示。

5.2  目录结构:

5.3 Memento(备忘录,如SolverState)

  • 备忘录存储原发器对象的内部状态。原发器根据需要决定备忘录存储原发器的哪些内部状态。
  • 防止原发器以外的其他对象访问备忘录。备忘录实际上有两个接口,管理者(caretaker)只能看到备忘录的窄接口—它只能将备忘录传递给其他对象。相反,原发器能够看到一个宽接口,允许它访问返回到先前状态所需的所有数据。理想的情况是只允许生成本备忘录的那个原发器访问本备忘录的内部状态。
export default  class ConstraintSolverMemento {
    state;
	constructor() {
		
    }
	Setstate(state)
	{
		this.state=state;
	}
	Getstate()
	{
		return this.state; 
	}
	 
	 
}

5.4 Originator(原发器,如ConstraintSolver)

  • 原发器创建一个备忘录,用以记录当前时刻它的内部状态。
  • 使用备忘录恢复内部状态.。

export default  class ConstraintSolver {
	static #instance;//静态私有变量
	ItemChildrens=[];
	
	constructor() {
			   if(!ConstraintSolver.#instance){
			              ConstraintSolver.#instance = this
			    }
			   return ConstraintSolver.#instance
	}
	static Instance() {
			  if (!this.#instance) this.#instance = new ConstraintSolver();
			      return this.#instance;
	}
	Solve()
	{
		for(let n=0;n<this.ItemChildrens.length;n++)
		{
			let item=this.ItemChildrens[n];
		    item.link=this.caclLink(item.start.rect,item.end.rect);
		}	 
	}
	caclLink(start,end)
	{
		/**
		 * 以end为基准,形成2条直线,start的中心为点,形成4种关系。
		 * 1-start的中心在end上面
		 * 2-start的中心在end右面
		 * 3-start的中心在end下面
		 * 4-start的中心在end左面
		 */
		let startX=start.startx;
		let startY=start.starty;
		let startEndX=start.startx+start.width;
		let startEndY=start.starty+start.height;
		
		let startCenterX=startX+start.width/2;
		let startCenterY=startY+start.height/2;
		
		let endX=end.startx;
		let endY=end.starty ;
		let endEndX=end.startx+end.width;
		let endEndY=end.starty+end.height;
		
		let endCenterX=endX+end.width/2;
		let endCenterY=endY+end.height/2;
		
		let direction=this.Cacldirc(startCenterX,startCenterY,end);
		let postStart={x:0,y:0};//start的线开始点
		let postEnd={x:0,y:0};//End的线结束点
		if(direction==1)  // up
		{
			postStart.x=startCenterX;
			postStart.y=startEndY;
			postEnd.x=endCenterX;
			postEnd.y=endY;
		}else if (direction==2) // right 
		{
			postStart.x=startX;
			postStart.y=startCenterY;
			postEnd.x=endEndX;
			postEnd.y=endCenterY;
		}
		else if (direction==3 ) //down
		{
			postStart.x=startCenterX;
			postStart.y=startY;
			postEnd.x=endEndX;
			postEnd.y=endEndY;
		}else // left
		{
			postStart.x=startEndX;
			postStart.y=startCenterY;
			postEnd.x=endX;
			postEnd.y=endCenterY;
		}
		return {postStart:postStart,postEnd:postEnd};
	}
	
	Cacldirc(startCenterX,endCenterY,end)
	{
		let endX=end.startx;
		let endY= -end.starty;
		let endEndX=end.startx+end.width;
		let endEndY=-(end.starty+end.height);
		
		let line1Post1={x:endX,y:endY};
		let line1Post2={x:endEndX,y:endEndY};
		 
		let line2Post1={x:endEndX,y:endY};
		let line2Post2={x:endX,y:endEndY};
		
		let  line1a=-(end.height/end.width);
		let  line1b=line1Post1.y-line1a*line1Post1.x;
		
		let  line2a=(end.height/end.width);
		let  line2b=line2Post1.y-line2a*line1Post1.x;
		
		let line1dir=endCenterY-line1a*startCenterX-line1b;
		
		let line2dir=endCenterY-line2a*startCenterX-line2b;
		
		if(line1dir>=0 && line2dir>=0)
			return 1;
		else if(line1dir>=0 && line2dir<0)
			return 2;
		else if(line1dir<0 && line2dir>=0)
			return 3;
		else  
			return 4;
	}
	AddConstraint(startGraphic,endGraplethic){
		let link={start:startGraphic,end:endGraplethic,link:{}};
		this.ItemChildrens.push(link);
	}
	RemoveConstraint(startGraphic,endGraphic){
		
	}
	CreateMemento()//重点
	{
		return this.ItemChildrens;
	}
	SetMemento(memento)//重点
	{
		this.ItemChildrens=memento;
	}
	
	Draw(ctx)
	{
		for(let n=0;n<this.ItemChildrens.length;n++)
		{
			let link=this.ItemChildrens[n].link;
			ctx.beginPath();
			ctx.moveTo(link.postStart.x,link.postStart.y);
			ctx.lineTo(link.postEnd.x,link.postEnd.y);
			ctx.stroke();
		 
		}	 
	}
}

5.5 Caretaker(负责人,如undomechanism)

  • 负责保存好备忘录。
  • 不能对备忘录的内容进行操作或检查。
import MoveCommand from './Command/impl/MoveCommand.js';

export default class UnDoRedo {

	#_Undocommands = [];
	#_Redocommands = [];

	constructor() {

	}
	GetUndoLength() {
		return this.#_Undocommands.length;
	}
	GetRedoLength() {
		return this.#_Redocommands.length;
	}

	Redo() {

		if (this.#_Redocommands.length != 0) {
			let command = this.#_Redocommands.pop();
			command.Execute();
			this.#_Undocommands.push(command);
		}
	}

	Undo() {

		if (this.#_Undocommands.length != 0) {
			let command = this.#_Undocommands.pop();
			command.UnExecute();
			this.#_Redocommands.push(command);
		}

	}

	InsertInUnDoRedoForMove(target, point) {
		let cmd = new MoveCommand(target, point);
		cmd.Execute();
		this.#_Undocommands.push(cmd);
		this.#_Redocommands = [];
	}

}

5.6  Application

import Rectangle  from './Widget/impl/Rectangle.js'; 
import Button  from './Widget/impl/Button.js'; 
import ConstraintSolver  from './Solver/ConstraintSolver.js'; 
import UnDoRedo  from './UnDoRedo.js'; 
 
export default class Application{
	ctx;
	zooRect;
	ItemChildrens=[];
	unDoRedo;
	// ItemZooChildrens=[];
	 
	solver= ConstraintSolver.Instance();
    constructor(ctx,zooRect) {
    	this.ctx=ctx;
		this.unDoRedo=new UnDoRedo();
		this.zooRect=zooRect;
		this.createElement(ctx);
		this.Draw();
    }
	Draw()
	{
		this.ctx.clearRect(this.zooRect.startx,this.zooRect.starty,this.zooRect.width,this.zooRect.height); 
		for(let n=0;n<this.ItemChildrens.length;n++)
		{
			let item=this.ItemChildrens[n];
			item.Draw();
		}
		this.solver.Draw(this.ctx);//直线的约束
	}
	onmousedown(e)
	{
		for(let n=0;n<this.ItemChildrens.length;n++)
		{
			let item=this.ItemChildrens[n];
			item.onmousedown(e);	
		}
		
		  // console.log(` onmousedown `);
		  this.Draw();
	}
	onmouseup(e)
	{
		for(let n=0;n<this.ItemChildrens.length;n++)
		{
			let item=this.ItemChildrens[n];
			item.onmouseup(e);	
		}
		// console.log(` onmouseup `);
		 this.Draw();
	}
	onmousemove(e)
	{
		for(let n=0;n<this.ItemChildrens.length;n++)
		{
			let item=this.ItemChildrens[n];
			item.onmousemove(e);	
		}
		this.Draw();
	}
  
	createElement(ctx)
	{
		let button1Pos={startx:150,starty:10,width:50,height:30};
		let button2Pos={startx:250,starty:10,width:50,height:30};
		let button1=new Button(ctx,'撤销',button1Pos);
		let button2=new Button(ctx,'重做',button2Pos);
		button1.AddClickEvent(
		{
			ClickEvent:(button,e) =>{ //撤销
				let undoLenth= this.unDoRedo.GetUndoLength();
				if(undoLenth>0)
				{
					this.unDoRedo.Undo();
					this.Draw();
				}
			}
		});
		
		button2.AddClickEvent(
		{
			ClickEvent:(button,e) =>{ //重做
				let undoLenth= this.unDoRedo.GetRedoLength();
				if(undoLenth>0)
				{
					this.unDoRedo.Redo();
					this.Draw();
				}
			}
		});

		let rect1Pos={startx:250,starty:50,width:100,height:50};
		let rect2Pos={startx:250,starty:150,width:100,height:50};
		let rect1=new Rectangle(ctx,'rect1',rect1Pos);
		rect1.AddDragEvent(this);
		rect1.AddDragOverEvent(this);
		
		let rect2=new Rectangle(ctx,'rect2',rect2Pos);
		this.ItemChildrens.push(rect1);
		this.ItemChildrens.push(rect2);
		this.ItemChildrens.push(button1);
		this.ItemChildrens.push(button2);
		
		this.solver.AddConstraint(rect1,rect2);
		this.solver.Solve();
	}
	DragEvent(target,e)
	{
		var x = e.clientX;
		var y = e.clientY;
		this.Draw();
	}
	DragOverEvent(target,e)
	{
		var x = e.clientX;
		var y = e.clientY;
		this.unDoRedo.InsertInUnDoRedoForMove(target,{x:e.clientX,y:e.clientY});
		console.log(`DragOver 移动 x:`+x +'y:'+y); 
		 
	}
   
 }

5.7 测试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.onmouseup = (e) => {
				cl.onmouseup(e);
			};
			canvas.onmousemove = (e) => {
				cl.onmousemove(e);
			};
		 
		</script>
	</head>
	<body>
		<canvas id="mycanvas" width=900px height=900px></canvas>

	</body>
</html>

测试结果:

六、源代码下载

        下载链接:https://pan.baidu.com/s/1XuPqp84cccBNVkbnMY3sKw 
         提取码:q2ut

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值