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 Mediator(中介者,如DialogDirector)
- 中介者定义一个接口用于与各同事(Colleague)对象通信。
1.2 ConcreteMediator(具体中介者,如FontDialogDirector)
- 具体中介者通过协调各同事对象实现协作行为。
- 了解并维护它的各个同事。
1.3 Colleagueclass(同事类,如ListBox,EntryField)
- 每一个同事类都知道它的中介者对象。
- 每一个同事对象在需与其他的同事通信的时候,与它的中介者通信。
二、意图
用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从 而使其耦合松散,而且可以独立地改变它们之间的交互。
三、适用性
- 一组对象以定义良好但是复杂的方式进行通信。产生的相互依赖关系结构混乱且难以理解。
- 一个对象引用其他很多对象并且直接与这些对象通信 ,导致难以复用该对象。
- 想定制一个分布在多个类中的行为,而又不想生成太多的子类。
四、优点和缺点
- 减少了子类生成Mediator将原本分布于多个对象间的行为集中在一起。改变这些行为只需生成Meditator的子类即可。这样各个Colleague类可被重用
- 它将各Colleague解耦Mediator有利于各Colleague间的松耦合.你可以独立的改变和复用各Colleague类和Mediator类。
- 它简化了对象协议用Mediator和各Colleague间的一对多的交互来代替多对多的交互。一对多的关系更易于理解、维护和扩展。
- 它对对象如何协作进行了抽象将中介作为一个独立的概念并将其封装在一个对象中,使你将注意力从对象各自本身的行为转移到它们之间的交互上来。这有助于弄清楚一个系统中的对象是如何交互的。
- 它使控制集中化中介者模式将交互的复杂性变为中介者的复杂性。因为中介者封装了协议,它可能变得比任一个Colleague都复杂。这可能使得中介者自身成为一个难于维护的庞然大物。
五、示例代码
5.1 动机
面向对象设计鼓励将行为分布到各个对象中。这种分布可能会导致对象间有许多连接。 在最坏的情况下,每一个对象都知道其他所有对象。
虽然将一个系统分割成许多对象通常可以增强可复用性 , 但是对象间相互连接的激增又会降低其可复用性。大量的相互连接使得一个对象似乎不太可能在没有其他对象的支持下工作 —系统表现为一个不可分割的整体。而且 ,对系统的行为进行任何较大的改动都十分困难, 因为行为被分布在许多对象中。结果是 , 你可能不得不定义很多子类以定制系统的行为。
例如,考虑一个图形用户界面中对话框的实现。对话框使用一个窗口来展现一系列的窗 口组件, 如按钮、菜单和输入域等, 如下图所示。
通常对话框中的窗口组件间存在依赖关系。例如 , 当一个特定的输入域为空时 , 某个按钮不能使用;在称为列表框的一列选项中选择一个表目可能会改变一个输入域的内容;反过来,在输入域中输入正文可能会自动的选择一个或多个列表框中相应的表目;一旦正文出现在输 入域中, 其他一些按钮可能就变得能够使用了,这些按钮允许用户做一些操作 , 比如改变或删 除这些正文所指的东西。
不同的对话框会有不同的窗口组件间的依赖关系。因此即使对话框显示相同类型的窗口 组件, 也不能简单地直接重用已有的窗口组件类 ; 而必须定制它们以反映特定对话框的依赖关 系。由于涉及很多个类,用逐个生成子类的办法来定制它们会很冗长。
可以通过将集体行为封装在一个单独的中介者(Mediator )对象中以避免这个问题。中介者 负责控制和协调一组对象间的交互。中介者充当一个中介以使组中的对象不再相互显式引用。 这些对象仅知道中介者, 从而减少了相互连接的数目。
5.2 示例UML
例如,FontDialogDirector可作为一个对话框中的窗口组件间的中介者。FontDialogDirector对象知道对话框中的各窗口组件,并协调它们之间的交互。它充当窗口组件间通信的中转中心,
如下图所示。
下面的交互图说明了各对象如何协作处理一个列表框中选项的变化。
DialogDirector是一个抽象类,它定义了一个对话框的总体行为。客户调用ShowDialog操作将对话框显示在屏幕上。CreateWidgets是创建一个对话框的窗口组件的抽象操作。WidgetChanged是另一个抽象操作;窗口组件调用它来通知它的导控者它们被改变了。DialogDirector的子类将重定义CreateWidgets以创建正确的窗口组件,并重定义WidgetChanged以处理其变化。
目录结构:
5.3 Mediator(中介者,如DialogDirector)
- 中介者定义一个接口用于与各同事(Colleague)对象通信。
export default class DialogDirector {
widgetChildrens=[];
constructor( ) {
}
ShowDialog() {
this.ctx.clearRect(this.rect.startx,this.rect.starty,this.rect.width,this.rect.height);
this.ctx.strokeRect(this.rect.startx,this.rect.starty,this.rect.width,this.rect.height);
for(let n=0;n<this.widgetChildrens.length;n++)
{
let widge=this.widgetChildrens[n];
widge.Draw();
}
}
CreateWidgets() {
}
WidgetChanged(widget) {
}
}
5.4 ConcreteMediator(具体中介者,如FontDialogDirector)
- 具体中介者通过协调各同事对象实现协作行为。
- 了解并维护它的各个同事。
import DialogDirector from '../DialogDirector.js';
import Button from '../../Widget/impl/Button.js';
import ListBox from '../../Widget/impl/ListBox.js';
import AntryField from '../../Widget/impl/AntryField.js';
export default class FontDialogDirector extends DialogDirector {
ctx;
rect;
zooChildrens=[];
fontListBox ;
cancelButton ;
confirmButton ;
antryField ;
constructor(ctx,rect) {
super();
this.ctx=ctx;
this.rect=rect;
this.CreateWidgets();
}
CreateWidgets() {
this.zooChildrens=[];
this.widgetChildrens=[];
this.ctx.strokeRect(this.rect.startx,this.rect.starty,this.rect.width,this.rect.height);
let fontList = ['黑体:SimHei',
'宋体:SimSun ',
'新宋体:NSimSun',
'仿宋:FangSong',
'楷体:KaiTi ',
'微软雅黑体:Microsoft YaHei'
];
let fontListZoo={startx:10,starty:10,width:180,height:200};
let cancelBuZoo={startx:100,starty:220,width:40,height:25};
let confirmBu={startx:150,starty:220,width:40,height:25};
let antryFieldZoo={startx:10,starty:250,width:180,height:25};
this.zooChildrens.push(fontListZoo);
this.zooChildrens.push(cancelBuZoo);
this.zooChildrens.push(confirmBu);
this.zooChildrens.push(antryFieldZoo);
this.fontListBox = new ListBox(this.ctx,fontList, this,fontListZoo);
this.cancelButton = new Button(this.ctx,'取消', this,cancelBuZoo);
this.confirmButton = new Button(this.ctx,'确定', this,confirmBu );
this.antryField =new AntryField(this.ctx,'', this,antryFieldZoo );
this.widgetChildrens.push(this.fontListBox);
this.widgetChildrens.push(this.cancelButton);
this.widgetChildrens.push(this.confirmButton);
this.widgetChildrens.push(this.antryField);
}
WidgetChanged(widget) {
let text=this.fontListBox.GetSelection();
this.antryField.SetText(text);
}
onmousedown(e)
{
let selectN=this.GetActiveItem(e);
if(selectN>-1)
{
for(let n=0;n<this.widgetChildrens.length;n++)
{
let widget=this.widgetChildrens[n];
if(n==selectN)
widget.SetIsClicked(true,e);
else
widget.SetIsClicked(false,e);
}
}
}
onmousemove(e)
{
let selectN=this.GetActiveItem(e);
if(selectN>-1)
{
for(let n=0;n<this.widgetChildrens.length;n++)
{
let widget=this.widgetChildrens[n];
if(n==selectN)
widget.SetIsMouseIn(true,e);
else
widget.SetIsMouseIn(false,e);
}
}
}
GetActiveItem(e)
{
var x = e.clientX;
var y = e.clientY;
if(x>this.rect.startx&&x<this.rect.startx+this.rect.width
&&y>this.rect.starty&&y<this.rect.starty+this.rect.height )
{
for(let n=0;n<this.zooChildrens.length;n++)
{
let zooChild=this.zooChildrens[n];
if(x>zooChild.startx&&x<zooChild.startx+zooChild.width
&&y>zooChild.starty&&y<zooChild.starty+zooChild.height )
{
return n;
}
}
}
else
return -1;
}
}
5.5 Colleagueclass(同事类,如ListBox,EntryField)
- 每一个同事类都知道它的中介者对象。
- 每一个同事对象在需与其他的同事通信的时候,与它的中介者通信。
import Widget from '../Widget.js';
export default class AntryField extends Widget {
text;
rect;
ctx;
constructor(ctx,text,director,rect) {
super(director);
this.text=text;
this.rect=rect;
this.ctx=ctx;
}
Draw()
{
this.ctx.strokeRect(this.rect.startx,this.rect.starty,this.rect.width,this.rect.height);
if(this.isMouseIn){
this.ctx.save();
this.ctx.fillStyle="red";
this.ctx.fillRect(this.rect.startx,this.rect.starty,this.rect.width,this.rect.height);
this.ctx.restore();
}
this.ctx.textAlign="center";
this.ctx.fillText(this.text,this.rect.startx+this.rect.width/2,this.rect.starty+3*this.rect.height/4);
}
Clicked()
{
console.log(this.name+` Clicked `);
}
SetText(text)
{
this.text=text;
this.Draw();
}
}
import Widget from '../Widget.js';
export default class Button extends Widget {
name;
rect;
ctx;
constructor(ctx,name,director,rect) {
super(director);
this.name=name;
this.rect=rect;
this.ctx=ctx;
}
Draw()
{
this.ctx.strokeRect(this.rect.startx,this.rect.starty,this.rect.width,this.rect.height);
if(this.isMouseIn){
this.ctx.save();
this.ctx.fillStyle="red";
this.ctx.fillRect(this.rect.startx,this.rect.starty,this.rect.width,this.rect.height);
this.ctx.restore();
}
this.ctx.textAlign="center";
this.ctx.fillText(this.name,this.rect.startx+this.rect.width/2,this.rect.starty+3*this.rect.height/4);
}
Clicked()
{
console.log(this.name+` Clicked `);
}
}
import Widget from '../Widget.js';
export default class ListBox extends Widget {
nameList=[];
rect;
itemHeight=20;
ctx;
zooChildrens=[];
selectN=-1;
constructor(ctx,nameList,director,rect) {
super(ctx,director);
this.nameList=[];
this.nameList=nameList;
this.rect=rect;
this.ctx=ctx;
}
Draw()
{
this.zooChildrens=[];
this.ctx.strokeRect(this.rect.startx,this.rect.starty,this.rect.width,this.rect.height);
let x=this.rect.startx+10;
let y=this.rect.starty+20;
for(let n=0;n<this.nameList.length;n++)
{
let name=this.nameList[n];
let itemRect={startx:x,starty:y,width:180,height:20};
if(this.isMouseIn&&n==this.selectN){
this.ctx.save();
this.ctx.fillStyle="blue";
this.ctx.fillRect(itemRect.startx,itemRect.starty,itemRect.width-30,itemRect.height);
this.ctx.restore();
}
this.ctx.textAlign="left";
this.ctx.fillText(name,itemRect.startx+10,itemRect.starty+3*itemRect.height/4);
// this.ctx.fillText(name,x,y);
this.zooChildrens.push(itemRect);
y=y+this.itemHeight;
if(y>this.rect.height) break;
}
}
GetSelection()
{
if(this.selectN>-1)
return this.nameList[this.selectN];
}
Clicked()
{
this.Changed();
}
SetIsClicked(isClicked,e)
{
this.isclicked=isClicked;
if(isClicked)
{
this.selectN=this.GetActiveItem(e);
if(this.selectN>-1)
this.Clicked();
}
}
SetIsMouseIn(isMouseIn,e)
{
this.isMouseIn=isMouseIn;
if(isMouseIn)
{
this.selectN=this.GetActiveItem(e);
}
}
GetActiveItem(e)
{
var x = e.clientX;
var y = e.clientY;
if(x>this.rect.startx&&x<this.rect.startx+this.rect.width
&&y>this.rect.starty&&y<this.rect.starty+this.rect.height )
{
for(let n=0;n<this.zooChildrens.length;n++)
{
let zooChild=this.zooChildrens[n];
if(x>zooChild.startx&&x<zooChild.startx+zooChild.width
&&y>zooChild.starty&&y<zooChild.starty+zooChild.height )
{
return n;
}
}
}
else
return -1;
}
}
5.6 Client
import FontDialogDirector from './DialogDirector/impl/FontDialogDirector.js';
export default class Client{
fontDialogDirector;
constructor(ctx,zooRect) {
this.fontDialogDirector=new FontDialogDirector(ctx,{startx:0,starty:0,width:300,height:300});
this.fontDialogDirector.ShowDialog();
}
onmousedown(e)
{
this.fontDialogDirector.onmousedown(e);
this.fontDialogDirector.ShowDialog();
}
onmousemove(e)
{
this.fontDialogDirector.onmousemove(e);
this.fontDialogDirector.ShowDialog();
}
}
5.7 测试HTML
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script type="module">
import Client from './Client.js';
var canvas = document.getElementById("mycanvas")
var ctx = canvas.getContext("2d") //create 2d object
let cl = new Client(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>
测试结果:
六、源代码下载
下载链接:https://pan.baidu.com/s/1XuPqp84cccBNVkbnMY3sKw
提取码:q2ut