JAVA SCRIPT设计模式--结构型--设计模式之Decorator装饰模式(9)

         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 Component(VisualComponent)  

  • 定义一个对象接口,可以给这些对象动态地添加职责。

1.2 ConcreteComponent(TextView)

  • 定义一个对象,可以给这个对象添加一些职责。

1.3 Decorator

  • 维持一个指向Component对象的指针,并定义一个与Component接口一致的接口。

1.4 ConcreteDecorator(BorderDecorator,ScrollDecorator)

  • 向组件添加职责。

二、意图

     动态地给一个对象添加一些额外的职责。就增加功能来说, Decorator模式相比生成子类 更为灵活。

三、适用性

  1. 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责
  2.  处理那些可以撤消的职责  
  3. 当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的扩展,为支持 每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。另一种情况可能是因为类 定义被隐藏,或类定义不能用于生成子类。

四、优点和缺点

  1. 比静态继承更灵活与对象的静态继承(多重继承)相比,Decorator模式提供了更加灵活的向对象添加职责的方式。可以用添加和分离的方法,用装饰在运行时刻增加和删除职责。相比之下,继承机制要求为每个添加的职责创建一个新的子类(例如,BorderScrollableTextView,BorderedTextView)。这会产生许多新的类,并且会增加系统的复杂度。此外,为一个特定的Component类提供多个不同的Decorator类,这就使得你可以对一些职责进行混合和匹配。使用Decorator模式可以很容易地重复添加一个特性,例如在TextView上添加双边框时,仅需将添加两个BorderDecorator即可。而两次继承Border类则极容易出错的。
  2. 避免在层次结构高层的类有太多的特征Decorator模式提供了一种“即用即付”的方法来添加职责。它并不试图在一个复杂的可定制的类中支持所有可预见的特征,相反,你可以定义一个简单的类,并且用Decorator类给它逐渐地添加功能。可以从简单的部件组合出复杂的功能。这样,应用程序不必为不需要的特征付出代价。同时也更易于不依赖于Decorator所扩展(甚至是不可预知的扩展)的类而独立地定义新类型的Decorator。扩展一个复杂类的时候,很可能会暴露与添加的职责无关的细节。
  3. Decorator与它的Component不一样Decorator是一个透明的包装。如果我们从对象标识的观点出发,一个被装饰了的组件与这个组件是有差别的,因此,使用装饰时不应该依赖对象标识
  4. 有许多小对象采用Decorator模式进行系统设计往往会产生许多看上去类似的小对象,这些对象仅仅在他们相互连接的方式上有所不同,而不是它们的类或是它们的属性值有所不同。尽管对于那些了解这些系统的人来说,很容易对它们进行定制,但是很难学习这些系统,排错也很困难。

五、示例代码

5.1  动机

        有时我们希望给某个对象而不是整个类添加一些功能。例如,一个图形用户界面工具箱允许你对任意一个用户界面组件添加一些特性,例如边框,或是一些行为,例如窗口滚动。

        使用继承机制是添加功能的一种有效途径,从其他类继承过来的边框特性可以被多个子类的实例所使用。但这种方法不够灵活,因为边框的选择是静态的,用户不能控制对组件加边框的方式和时机。

        一种较为灵活的方式是将组件嵌入另一个对象中,由这个对象添加边框。我们称这个嵌 入的对象为装饰。这个装饰与它所装饰的组件接口一致,因此它对使用该组件的客户透明 它将客户请求转发给该组件,并且可能在转发前后执行一些额外的动作(例如画一个边框)。 透明性使得你可以递归的嵌套多个装饰,从而可以添加任意多的功能,如下图所示。

         例如,假定有一个对象TextView,它可以在窗口中显示正文。缺省的TextView没有滚动条,因为我们可能有时并不需要滚动条。当需要滚动条时,我们可以用ScrollDecorator添加滚
动条。如果我们还想在TextView周围添加一个粗黑边框,可以使用BorderDecorator添加。因此只要简单地将这些装饰和TextView进行组合,就可以达到预期的效果。

        下面的对象图展示了如何将一个TextView对象与BorderDecorator以及ScrollDecorator对象组装起来产生一个具有边框和滚动条的文本显示窗口。

        

         ScrollDecorator和BorderDecorator类是Decorator类的子类。Decorator类是一个可视组件的抽象类,用于装饰其他可视组件,如下图所示。

        VisualComponent是一个描述可视对象的抽象类,它定义了绘制和事件处理的接口。注意Decorator类怎样将绘制请求简单地发送给它的组件,以及Decorator的子类如何扩展这个操作。
Decorator的子类为特定功能可以自由地添加一些操作。例如,如果其他对象知道界面中恰好有一个ScrollDecorator对象,这些对象就可以用ScrollDecorator对象的ScrollTo操作滚动这个界面。这个模式中有一点很重要,它使得在VisualComponent可以出现的任何地方都可以有装饰。因此,客户通常不会感觉到装饰过的组件与未装饰组件之间的差异,也不会与装饰产生任何依赖关系。
 

5.2  示例UML

目录结构:

5.2 Component(VisualComponent)  

  • 定义一个对象接口,可以给这些对象动态地添加职责。
export default  class VisualComponent {
	ctx;
    constructor(ctx) {
		this.ctx=ctx;
    }
    Draw() {   
		
    }
     
  }

5.3 ConcreteComponent(TextView)

  • 定义一个对象,可以给这个对象添加一些职责。
import VisualComponent  from '../VisualComponent.js';

export default  class TextView extends VisualComponent {
    x;y;
	constructor(ctx,x,y) {
		 super(ctx);
		 this.x=x;
		 this.y=y;
    }
    Draw() {
		this.ctx.font="40px Arial";
		this.ctx.fillText("Hello World!",this.x,this.y);
		console.log(` TextView Draw `);
    }

  } 

5.4 Decorator

  • 维持一个指向Component对象的指针,并定义一个与Component接口一致的接口。

import VisualComponent  from '../VisualComponent.js';
export default  class Decorator extends VisualComponent {
	visualComponent;
    constructor(ctx,visualComponent) {
		 super(ctx);
		 this.visualComponent=visualComponent;
    }
    Draw() {
		this.visualComponent.Draw();
    }
    
  } 

5.5 ConcreteDecorator(BorderDecorator,ScrollDecorator)

  • 向组件添加职责。

import Decorator  from '../Decorator.js';
export default  class BorderDecorator extends Decorator {
	pos;
    constructor(ctx,visualComponent,pos) {
		 super(ctx,visualComponent); 
		 this.pos=pos;
    }
    Draw() {
		super.Draw();
		this.DrawBorder();
    }
	DrawBorder()
	{
		this.ctx.beginPath();
		this.ctx.lineWidth="2";
		this.ctx.strokeStyle="red";
		this.ctx.rect(this.pos.x,this.pos.y,this.pos.width,this.pos.height);
		this.ctx.stroke();
	}
    
  } 
import Decorator  from '../Decorator.js';
export default  class ScrollDecorator extends Decorator {
	pos;
    constructor(ctx,visualComponent,pos) {
		 super(ctx,visualComponent); 
		 this.pos=pos;
    }
    Draw() {
		super.Draw();
		this.DrawScroll();
    }
	DrawScroll()
	{
		this.ctx.beginPath();
		this.ctx.lineWidth="4";
		this.ctx.fillStyle="#0000ff";
		this.ctx.fillRect(this.pos.x,this.pos.y,this.pos.width,this.pos.height);
		// this.ctx.rect(this.pos.x,this.pos.y,this.pos.width,this.pos.height);
		this.ctx.stroke();
	}
    
  } 

5.6 Client

import VisualComponent  from './VisualComponent/VisualComponent.js';
import TextView  from './VisualComponent/impl/TextView.js';
import BorderDecorator  from './VisualComponent/Decorator/impl/BorderDecorator.js';
import ScrollDecorator  from './VisualComponent/Decorator/impl/ScrollDecorator.js';
 
 
export default class Client{
    main(ctx){
	  /*只是一个装饰的例子,还原原文中的装饰思路,忽略硬代码部分*/
	    let atextView =new TextView(ctx,40,150);//Graphic
		let ascrollDecorator =new ScrollDecorator(ctx,atextView,{x:280,y:0,width:15,height:300});//Graphic
		let aborderDecorator =new BorderDecorator(ctx,ascrollDecorator,{x:0,y:0,width:300,height:300});//Graphic
	
		aborderDecorator.Draw();
    } 
 }

5.7 测试HTML

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
 
 
 <script  type="module" >
 import Client  from './Client.js'; 
 var x=document.getElementById("mycanvas")
 var ctx=x.getContext("2d") //create 2d object
let cl=new Client();
cl.main(ctx)

 </script>
</head>
<body>
<canvas id="mycanvas" width=300px height=300px></canvas>
    
</body>
</html>

测试结果:

六、源代码下载

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值