2006年07月04日

HTML事件的控件触发
http://dev.csdn.net/develop/article/20/20566.shtm
JavaScript通用库(二)
http://dev.csdn.net/article/28/28057.shtm
破解网页禁止鼠标右键的技巧
http://dev.csdn.net/develop/article/19/19419.shtm
如何利用JAVASCRIPT实现浮动广告
http://community.csdn.net/expert/topic/3226/3226990.xml?temp=.5731165
加入收藏夹大全
http://www.588188.com/netbook/tech/other/html/other.ohchina.6.htm
客户端脚本真藏
http://www.pcdog.com/info/2974.htm
//打开模式对话框
function doSelectUser(txtId)
{

strFeatures="dialogWidth=500px;dialogHeight=360px;center=yes;middle=yes ;help=no;status=no;scroll=no";
var url,strReturn;

url="selUser.aspx";

strReturn=window.showModalDialog(url,”,strFeatures);

}

//返回模式对话框的值
function okbtn_onclick()
{
var commstr=”;

window.returnValue=commstr;

window.close() ;
}
全屏幕打开 IE 窗口
var winWidth=screen.availWidth ;
var winHeight=screen.availHeight-20;
window.open("main.aspx","surveyWindow","toolbar=no,width="+ winWidth +",height="+ winHeight +",top=0,left=0,scrollbars=yes,resizable=yes,center:yes,statusbars=yes");
break
//脚本中中使用xml
function initialize() {
var xmlDoc
var xslDoc

xmlDoc = new ActiveXObject(‘Microsoft.XMLDOM’)
xmlDoc.async = false;

xslDoc = new ActiveXObject(‘Microsoft.XMLDOM’)
xslDoc.async = false;

xmlDoc.load("tree.xml")
xslDoc.load("tree.xsl")

folderTree.innerHTML = xmlDoc.documentElement.transformNode(xslDoc)
}

Top

回复人:happyjun2000(蓝色游侠∮是非成败转头空,以后不再up了) ( ) 信誉:89 2004-10-27 9:11:08 得分:0

?
一、验证类
1、数字验证内
1.1 整数
1.2 大于0的整数 (用于传来的ID的验证)
1.3 负整数的验证
1.4 整数不能大于iMax
1.5 整数不能小于iMin
2、时间类
2.1 短时间,形如 (13:04:06)
2.2 短日期,形如 (2003-12-05)
2.3 长时间,形如 (2003-12-05 13:04:06)
2.4 只有年和月。形如(2003-05,或者2003-5)
2.5 只有小时和分钟,形如(12:03)
3、表单类
3.1 所有的表单的值都不能为空
3.2 多行文本框的值不能为空。
3.3 多行文本框的值不能超过sMaxStrleng
3.4 多行文本框的值不能少于sMixStrleng
3.5 判断单选框是否选择。
3.6 判断复选框是否选择.
3.7 复选框的全选,多选,全不选,反选
3.8 文件上传过程中判断文件类型
4、字符类
4.1 判断字符全部由a-Z或者是A-Z的字字母组成
4.2 判断字符由字母和数字组成。
4.3 判断字符由字母和数字,下划线,点号组成.且开头的只能是下划线和字母
4.4 字符串替换函数.Replace();
5、浏览器类
5.1 判断浏览器的类型
5.2 判断ie的版本
5.3 判断客户端的分辨率

6、结合类
6.1 email的判断。
6.2 手机号码的验证
6.3 身份证的验证

二、功能类

1、时间与相关控件类
1.1 日历
1.2 时间控件
1.3 万年历
1.4 显示动态显示时钟效果(文本,如OA中时间)
1.5 显示动态显示时钟效果 (图像,像手表)
2、表单类
2.1 自动生成表单
2.2 动态添加,修改,删除下拉框中的元素
2.3 可以输入内容的下拉框
2.4 多行文本框中只能输入iMax文字。如果多输入了,自动减少到iMax个文字(多用于短信发送)

3、打印类
3.1 打印控件
4、事件类
4.1 屏蔽右键
4.2 屏蔽所有功能键
4.3 –> 和<– F5 F11,F9,F1
4.4 屏蔽组合键ctrl+N
5、网页设计类
5.1 连续滚动的文字,图片(注意是连续的,两段文字和图片中没有空白出现)
5.2 html编辑控件类
5.3 颜色选取框控件
5.4 下拉菜单
5.5 两层或多层次的下拉菜单
5.6 仿IE菜单的按钮。(效果如rongshuxa.com的导航栏目)
5.7 状态栏,title栏的动态效果(例子很多,可以研究一下)
5.8 双击后,网页自动滚屏
6、树型结构。
6.1 asp+SQL版
6.2 asp+xml+sql版
6.3 java+sql或者java+sql+xml
7、无边框效果的制作
8、连动下拉框技术
9、文本排序
10,画图类,含饼、柱、矢量贝滋曲线
11,操纵客户端注册表类
12,DIV层相关(拖拽、显示、隐藏、移动、增加)
13,TABLAE相关(客户端动态增加行列,模拟进度条,滚动列表等)
14,各种<object classid=>相关类,如播放器,flash与脚本互动等
16, 刷新/模拟无刷新 异步调用类(XMLHttp或iframe,frame)

一、验证类
1、数字验证内
1.1 整数
/^(-|\+)?\d+$/.test(str)
1.2 大于0的整数 (用于传来的ID的验证)
/^\d+$/.test(str)
1.3 负整数的验证
/^-\d+$/.test(str)
2、时间类
2.1 短时间,形如 (13:04:06)
function isTime(str)
{
var a = str.match(/^(\d{1,2})(:)?(\d{1,2})\2(\d{1,2})$/);
if (a == null) {alert(‘输入的参数不是时间格式’); return false;}
if (a[1]>24 || a[3]>60 || a[4]>60)
{
alert("时间格式不对");
return false
}
return true;
}
2.2 短日期,形如 (2003-12-05)
function strDateTime(str)
{
var r = str.match(/^(\d{1,4})(-|\/)(\d{1,2})\2(\d{1,2})$/);
if(r==null)return false;
var d= new Date(r[1], r[3]-1, r[4]);
return (d.getFullYear()==r[1]&&(d.getMonth()+1)==r[3]&&d.getDate()==r[4]);
}
2.3 长时间,形如 (2003-12-05 13:04:06)
function strDateTime(str)
{
var reg = /^(\d{1,4})(-|\/)(\d{1,2})\2(\d{1,2}) (\d{1,2}):(\d{1,2}):(\d{1,2})$/;
var r = str.match(reg);
if(r==null)return false;
var d= new Date(r[1], r[3]-1,r[4],r[5],r[6],r[7]);
return (d.getFullYear()==r[1]&&(d.getMonth()+1)==r[3]&&d.getDate()==r[4]&&d.getHours()==r[5]&&d.getMinutes()==r[6]&&d.getSeconds()==r[7]);
}
2.4 只有年和月。形如(2003-05,或者2003-5)
2.5 只有小时和分钟,形如(12:03)
3、表单类
3.1 所有的表单的值都不能为空
<input onblur="if(this.value.replace(/^\s+|\s+$/g,”)==”)alert(‘不能为空!’)">
3.2 多行文本框的值不能为空。
3.3 多行文本框的值不能超过sMaxStrleng
3.4 多行文本框的值不能少于sMixStrleng
3.5 判断单选框是否选择。
3.6 判断复选框是否选择.
3.7 复选框的全选,多选,全不选,反选
3.8 文件上传过程中判断文件类型
4、字符类
4.1 判断字符全部由a-Z或者是A-Z的字字母组成
<input onblur="if(/[^a-zA-Z]/g.test(this.value))alert(‘有错’)">
4.2 判断字符由字母和数字组成。
<input onblur="if(/[^0-9a-zA-Z]/g.test(this.value))alert(‘有错’)">
4.3 判断字符由字母和数字,下划线,点号组成.且开头的只能是下划线和字母
/^([a-zA-z_]{1})([\w]*)$/g.test(str)
4.4 字符串替换函数.Replace();
5、浏览器类
5.1 判断浏览器的类型
window.navigator.appName
5.2 判断ie的版本
window.navigator.appVersion
5.3 判断客户端的分辨率
window.screen.height; window.screen.width;

6、结合类
6.1 email的判断。
function ismail(mail)
{
return(new RegExp(/^\w+((-\w+)|(\.\w+))*\@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/).test(mail));
}
6.2 手机号码的验证
6.3 身份证的验证
function isIdCardNo(num)
{
if (isNaN(num)) {alert("输入的不是数字!"); return false;}
var len = num.length, re;
if (len == 15)
re = new RegExp(/^(\d{6})()?(\d{2})(\d{2})(\d{2})(\d{3})$/);
else if (len == 18)
re = new RegExp(/^(\d{6})()?(\d{4})(\d{2})(\d{2})(\d{3})(\d)$/);
else {alert("输入的数字位数不对!"); return false;}
var a = num.match(re);
if (a != null)
{
if (len==15)
{
var D = new Date("19"+a[3]+"/"+a[4]+"/"+a[5]);
var B = D.getYear()==a[3]&&(D.getMonth()+1)==a[4]&&D.getDate()==a[5];
}
else
{
var D = new Date(a[3]+"/"+a[4]+"/"+a[5]);
var B = D.getFullYear()==a[3]&&(D.getMonth()+1)==a[4]&&D.getDate()==a[5];
}
if (!B) {alert("输入的身份证号 "+ a[0] +" 里出生日期不对!"); return false;}
}
return true;
}

画图:
<OBJECT
id=S
style="LEFT: 0px; WIDTH: 392px; TOP: 0px; HEIGHT: 240px"
height=240
width=392
classid="clsid:369303C2-D7AC-11D0-89D5-00A0C90833E6">
</OBJECT>
<SCRIPT>
S.DrawingSurface.ArcDegrees(0,0,0,30,50,60);
S.DrawingSurface.ArcRadians(30,0,0,30,50,60);
S.DrawingSurface.Line(10,10,100,100);
</SCRIPT>

Top

回复人:happyjun2000(蓝色游侠∮是非成败转头空,以后不再up了) ( ) 信誉:89 2004-10-27 9:11:15 得分:0

?
写注册表:
<SCRIPT>
var WshShell = WScript.CreateObject("WScript.Shell");
WshShell.RegWrite ("HKCU\\Software\\ACME\\FortuneTeller\\", 1, "REG_BINARY");
WshShell.RegWrite ("HKCU\\Software\\ACME\\FortuneTeller\\MindReader", "Goocher!", "REG_SZ");
var bKey = WshShell.RegRead ("HKCU\\Software\\ACME\\FortuneTeller\\");
WScript.Echo (WshShell.RegRead ("HKCU\\Software\\ACME\\FortuneTeller\\MindReader"));
WshShell.RegDelete ("HKCU\\Software\\ACME\\FortuneTeller\\MindReader");
WshShell.RegDelete ("HKCU\\Software\\ACME\\FortuneTeller\\");
WshShell.RegDelete ("HKCU\\Software\\ACME\\");
</SCRIPT>

TABLAE相关(客户端动态增加行列)
<HTML>
<SCRIPT LANGUAGE="JScript">
function numberCells() {
var count=0;
for (i=0; i < document.all.mytable.rows.length; i++) {
for (j=0; j < document.all.mytable.rows(i).cells.length; j++) {
document.all.mytable.rows(i).cells(j).innerText = count;
count++;
}
}
}
</SCRIPT>
<BODY onload="numberCells()">
<TABLE id=mytable border=1>
<TR><TH> </TH><TH> </TH><TH> </TH><TH> </TH></TR>
<TR><TD> </TD><TD> </TD><TD> </TD><TD> </TD></TR>
<TR><TD> </TD><TD> </TD><TD> </TD><TD> </TD></TR>
</TABLE>
</BODY>
</HTML>

1.身份证严格验证:

<script>
var aCity={11:"北京",12:"天津",13:"河北",14:"山西",15:"内蒙古",21:"辽宁",22:"吉林",23:"黑龙江 ",31:"上海",32:"江苏",33:"浙江",34:"安徽",35:"福建",36:"江西",37:"山东",41:"河南",42:"湖北 ",43:"湖南",44:"广东",45:"广西",46:"海南",50:"重庆",51:"四川",52:"贵州",53:"云南",54:"西藏 ",61:"陕西",62:"甘肃",63:"青海",64:"宁夏",65:"新疆",71:"台湾",81:"香港",82:"澳门",91:"国外 "}

function cidInfo(sId){
var iSum=0
var info=""
if(!/^\d{17}(\d|x)$/i.test(sId))return false;
sId=sId.replace(/x$/i,"a");
if(aCity[parseInt(sId.substr(0,2))]==null)return "Error:非法地区";
sBirthday=sId.substr(6,4)+"-"+Number(sId.substr(10,2))+"-"+Number(sId.substr(12,2));
var d=new Date(sBirthday.replace(/-/g,"/"))
if(sBirthday!=(d.getFullYear()+"-"+ (d.getMonth()+1) + "-" + d.getDate()))return "Error:非法生日";
for(var i = 17;i>=0;i –) iSum += (Math.pow(2,i) % 11) * parseInt(sId.charAt(17 – i),11)
if(iSum%11!=1)return "Error:非法证号";
return aCity[parseInt(sId.substr(0,2))]+","+sBirthday+","+(sId.substr(16,1)%2?"男":"女")
}

document.write(cidInfo("380524198002300016"),"<br/>");
document.write(cidInfo("340524198002300019"),"<br/>")
document.write(cidInfo("340524197711111111"),"<br/>")
document.write(cidInfo("34052419800101001x"),"<br/>");
</script>

2.验证IP地址
<SCRIPT LANGUAGE="JavaScript">
function isip(s){
var check=function(v){try{return (v<=255 && v>=0)}catch(x){return false}};
var re=s.split(".")
return (re.length==4)?(check(re[0]) && check(re[1]) && check(re[2]) && check(re[3])):false
}

var s="202.197.78.129";
alert(isip(s))
</SCRIPT>

3.加sp1后还能用的无边框窗口!!
<HTML XMLNS:IE>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<IE:Download ID="include" STYLE="behavior:url(#default#download)" />
<title>Chromeless Window</title>

<SCRIPT LANGUAGE="JScript">
/*— Special Thanks For andot —*/

/*
This following code are designed and writen by Windy_sk <seasonx@163.net>
You can use it freely, but u must held all the copyright items!
*/

/*— Thanks For andot Again —*/

var CW_width= 400;
var CW_height= 300;
var CW_top= 100;
var CW_left= 100;
var CW_url= "/";
var New_CW= window.createPopup();
var CW_Body= New_CW.document.body;
var content= "";
var CSStext= "margin:1px;color:black; border:2px outset;border-style:expression(onmouseout=onmouseup=function(){this.style.borderStyle=’outset’}, onmousedown=function(){if(event.button!=2)this.style.borderStyle=’inset’});background-color:buttonface;width:16px;height:14px;font-size:12px;line-height:11px;cursor:Default;";

//Build Window
include.startDownload(CW_url, function(source){content=source});

function insert_content(){
var temp = "";
CW_Body.style.overflow= "hidden";
CW_Body.style.backgroundColor= "white";
CW_Body.style.border= "solid black 1px";
content = content.replace(/<a ([^>]*)>/g,"<a onclick=’parent.open(this.href);return false’ $1>");
temp += "<table width=100% height=100% cellpadding=0 cellspacing=0 border=0>";
temp += "<tr style=’;font-size:12px;background:#0099CC;height:20;cursor:default’ ondblclick=\"Max.innerText=Max.innerText==’1′?’2′:’1′;parent.if_max=!parent.if_max;parent.show_CW();\" onmouseup=’parent.drag_up(event)’ onmousemove=’parent.drag_move(event)’ onmousedown=’parent.drag_down(event)’ onselectstart=’return false’ oncontextmenu=’return false’>";
temp += "<td style=’color:#ffffff;padding-left:5px’>Chromeless Window For IE6 SP1</td>";
temp += "<td style=’color:#ffffff;padding-right:5px;’ align=right>";
temp += "<span id=Help onclick=\"alert(‘Chromeless Window For IE6 SP1 – Ver 1.0\\n\\nCode By Windy_sk\\n\\nSpecial Thanks For andot’)\" style=\""+CSStext+"font-family:System;padding-right:2px;\">?</span>";
temp += "<span id=Min onclick=’parent.New_CW.hide();parent.blur()’ style=\""+CSStext+"font-family:Webdings;\" title=’Minimum’>0</span>";
temp += "<span id=Max onclick=\"this.innerText=this.innerText==’1′?’2′:’1′;parent.if_max=!parent.if_max;parent.show_CW();\" style=\""+CSStext+"font-family:Webdings;\" title=’Maximum’>1</span>";
temp += "<span id=Close onclick=’parent.opener=null;parent.close()’ style=\""+CSStext+"font-family:System;padding-right:2px;\" title=’Close’>x</span>";
temp += "</td></tr><tr><td colspan=2>";
temp += "<div id=include style=’overflow:scroll;overflow-x:hidden;overflow-y:auto; HEIGHT: 100%; width:"+CW_width+"’>";
temp += content;
temp += "</div>";
temp += "</td></tr></table>";
CW_Body.innerHTML = temp;
}

setTimeout("insert_content()",1000);

var if_max = true;
function show_CW(){
window.moveTo(10000, 10000);
if(if_max){
New_CW.show(CW_top, CW_left, CW_width, CW_height);
if(typeof(New_CW.document.all.include)!="undefined"){
New_CW.document.all.include.style.width = CW_width;
New_CW.document.all.Max.innerText = "1";
}

}else{
New_CW.show(0, 0, screen.width, screen.height);
New_CW.document.all.include.style.width = screen.width;
}
}

window.onfocus = show_CW;
window.onresize = show_CW;

// Move Window
var drag_x,drag_y,draging=false

function drag_move(e){
if (draging){
New_CW.show(e.screenX-drag_x, e.screenY-drag_y, CW_width, CW_height);
return false;
}
}

function drag_down(e){
if(e.button==2)return;
if(New_CW.document.body.offsetWidth==screen.width && New_CW.document.body.offsetHeight==screen.height)return;
drag_x=e.clientX;
drag_y=e.clientY;
draging=true;
e.srcElement.setCapture();
}

function drag_up(e){
draging=false;
e.srcElement.releaseCapture();
if(New_CW.document.body.offsetWidth==screen.width && New_CW.document.body.offsetHeight==screen.height) return;
CW_top = e.screenX-drag_x;
CW_left = e.screenY-drag_y;
}

</SCRIPT>
</HTML>

Top

回复人:tingpig(汀) ( ) 信誉:100 2004-10-27 9:32:46 得分:2

?
贴两个关于treeview的
<script language="javascript">
<!–
//初始化选中节点
function initchecknode()
{
var node=TreeView1.getTreeNode("1");
node.setAttribute("Checked","true");
setcheck(node,"true");
FindCheckedFromNode(TreeView1);
}
//oncheck事件
function tree_oncheck(tree)
{
var node=tree.getTreeNode(tree.clickedNodeIndex);
var Pchecked=tree.getTreeNode(tree.clickedNodeIndex).getAttribute("checked");
setcheck(node,Pchecked);
document.all.checked.value="";
document.all.unchecked.value="";
FindCheckedFromNode(TreeView1);
}
//设置子节点选中
function setcheck(node,Pc)
{
var i;
var ChildNode=new Array();
ChildNode=node.getChildren();

if(parseInt(ChildNode.length)==0)
return;
else
{
for(i=0;i<ChildNode.length;i++)
{
var cNode;
cNode=ChildNode[i];
if(parseInt(cNode.getChildren().length)!=0)
setcheck(cNode,Pc);
cNode.setAttribute("Checked",Pc);
}
}
}
//获取所有节点状态
function FindCheckedFromNode(node) {
var i = 0;
var nodes = new Array();
nodes = node.getChildren();

for (i = 0; i < nodes.length; i++) {
var cNode;
cNode=nodes[i];
if (cNode.getAttribute("Checked"))
AddChecked(cNode);
else
AddUnChecked(cNode);

if (parseInt(cNode.getChildren().length) != 0 ) {
FindCheckedFromNode(cNode);
}
}
}
//添加选中节点
function AddChecked(node) {
document.all.checked.value += node.getAttribute("NodeData");
document.all.checked.value += ‘,’;
}
//添加未选中节点
function AddUnChecked(node) {
document.all.unchecked.value += node.getAttribute("NodeData");
document.all.unchecked.value += ‘,’;
}
//–>
</script>

在#提示符下执行命令
cp /dev/cdrom XXXXX.iso
XXXXX.iso即为需要命名的ISO文件名
执行之后,光盘上所有文件就被映射成XXXXX.iso

至于如何加载嘛
请看下面
还是在#提示符下执行命令
rm -rf /dev/cdrom
ln -s /dev/loop7 /dev/cdrom
losetup /dev/loop7 /PATH(iso文件路径)
mount /mnt/cdrom

如果需要换盘
losetup -d /dev/loop7
再重复
losetup /dev/loop7 /PATH(iso文件路径)
mount /mnt/cdrom

如果是普通含有iso的光盘
可以直接使用命令
mount -t iso9660 -o loop /../*.iso /path
/…/*.iso 是iso文件路径
/path 是挂载点

2006年06月06日

Memento定义:
memento是一个保存另外一个对象内部状态拷贝的对象.这样以后就可以将该对象恢复到原先保存的状态.

Memento模式相对也比较好理解,我们看下列代码:

public
class Originator {

   private int number;

  private File file =
null;

  public Originator(){}

  // 创建一个Memento
  public Memento
getMemento(){
    return new Memento(this);
  }

  //
恢复到原始值
  public void setMemento(Memento m){
     number =
m.number;
     file =
m.file;
  }

}

 
 

我们再看看Memento类:

private class
Memento implements java.io.Serializable{

  private int
number;

  private File file = null;

  public Memento( Originator
o){

    number = o.number;
    file =
o.file;

  }

}

 
 

可见
Memento中保存了Originator中的number和file的值.
通过调用Originator中number和file值改变的话,通过调用setMemento()方法可以恢复.

Memento模式的缺点是耗费大,如果内部状态很多,再保存一份,无意要浪费大量内存.

Memento模式在Jsp+Javabean中的应用
在Jsp应用中,我们通常有很多表单要求用户输入,比如用户注册,需要输入姓名和Email等,
如果一些表项用户没有填写或者填写错误,我们希望在用户按"提交Submit"后,通过Jsp程序检查,发现确实有未填写项目,则在该项目下红字显示警告或错误,同时,还要显示用户刚才已经输入的表项.

如下图中
First Name是用户已经输入,Last
Name没有输入,我们则提示红字警告.:

[缺图]

这种技术的实现,就是利用了Javabean的scope="request"或scope="session"特性,也就是Memento模式.

具体示例和代码见
JavaWorld的英文原文 , Javabean表单输入特性参见我的另外一篇文章.

Template定义:
定义一个操作中算法的骨架,将一些步骤的执行延迟到其子类中.

其实Java的抽象类本来就是Template模式,因此使用很普遍.而且很容易理解和使用,我们直接以示例开始:

public
abstract class Benchmark
{
  /**
  *
下面操作是我们希望在子类中完成
  */
  public abstract void benchmark();


  /**
  * 重复执行benchmark次数
  */
  public final long repeat (int
count) {
    if (count <= 0)
      return 0;
    else
{
      long startTime = System.currentTimeMillis();

    for (int i =
0; i < count; i++)
      benchmark();

    long stopTime =
System.currentTimeMillis();
    return stopTime –
startTime;
  }
}
}
 

在上例中,我们希望重复执行benchmark()操作,但是对benchmark()的具体内容没有说明,而是延迟到其子类中描述:

public
class MethodBenchmark extends Benchmark
{
  /**
  *
真正定义benchmark内容
  */
  public void benchmark() {

    for (int i =
0; i < Integer.MAX_VALUE; i++){
      System.out.printtln("i="+i); 
  
   
}
  }
}
 

至此,Template模式已经完成,是不是很简单?看看如何使用:

Benchmark
operation = new MethodBenchmark();
long duration =
operation.repeat(Integer.parseInt(args[0].trim()));
System.out.println("The
operation took " + duration + "
milliseconds");

 

也许你以前还疑惑抽象类有什么用,现在你应该彻底明白了吧?
至于这样做的好处,很显然啊,扩展性强,以后Benchmark内容变化,我只要再做一个继承子类就可以,不必修改其他应用代码.

Flyweight定义:
避免大量拥有相同内容的小类的开销(如耗费内存),使大家共享一个类(元类).

为什么使用?
面向对象语言的原则就是一切都是对象,但是如果真正使用起来,有时对象数可能显得很庞大,比如,字处理软件,如果以每个文字都作为一个对象,几千个字,对象数就是几千,无疑耗费内存,那么我们还是要"求同存异",找出这些对象群的共同点,设计一个元类,封装可以被共享的类,另外,还有一些特性是取决于应用(context),是不可共享的,这也Flyweight中两个重要概念内部状态intrinsic和外部状态extrinsic之分.

说白点,就是先捏一个的原始模型,然后随着不同场合和环境,再产生各具特征的具体模型,很显然,在这里需要产生不同的新对象,所以Flyweight模式中常出现Factory模式.Flyweight的内部状态是用来共享的,Flyweight
factory负责维护一个Flyweight
pool(模式池)来存放内部状态的对象.

Flyweight模式是一个提高程序效率和性能的模式,会大大加快程序的运行速度.应用场合很多:比如你要从一个数据库中读取一系列字符串,这些字符串中有许多是重复的,那么我们可以将这些字符串储存在Flyweight池(pool)中.

如何使用?

我们先从Flyweight抽象接口开始:

public
inte***ce Flyweight
{
  public void operation( ExtrinsicState state
);
}

//用于本模式的抽象数据类型(自行设计)
public inte***ce ExtrinsicState {
}
 

下面是接口的具体实现(ConcreteFlyweight) ,并为内部状态增加内存空间,
ConcreteFlyweight必须是可共享的,它保存的任何状态都必须是内部(intrinsic),也就是说,ConcreteFlyweight必须和它的应用环境场合无关.

public
class ConcreteFlyweight implements Flyweight {
  private IntrinsicState
state;
  
  public void operation( ExtrinsicState state )
  {

      //具体操作
  }

}

 

当然,并不是所有的Flyweight具体实现子类都需要被共享的,所以还有另外一种不共享的ConcreteFlyweight:

public
class UnsharedConcreteFlyweight implements Flyweight {

  public void
operation( ExtrinsicState state ) { }

}
 

Flyweight
factory负责维护一个Flyweight池(存放内部状态),当客户端请求一个共享Flyweight时,这个factory首先搜索池中是否已经有可适用的,如果有,factory只是简单返回送出这个对象,否则,创建一个新的对象,加入到池中,再返回送出这个对象.池

public
class FlyweightFactory {
  //Flyweight pool
  private Hashtable
flyweights = new Hashtable();

  public Flyweight getFlyweight( Object
key ) {

    Flyweight flyweight = (Flyweight) flyweights.get(key);


    if( flyweight == null )
{
      //产生新的ConcreteFlyweight
      flyweight = new ConcreteFlyweight();

      flyweights.put( key, flyweight );
    }

    return
flyweight;
  }
}

 

至此,Flyweight模式的基本框架已经就绪,我们看看如何调用:

FlyweightFactory factory =
new FlyweightFactory();
Flyweight fly1 = factory.getFlyweight( "Fred" );

Flyweight fly2 = factory.getFlyweight( "Wilma"
);
……

从调用上看,好象是个纯粹的Factory使用,但奥妙就在于Factory的内部设计上.

Flyweight模式在XML等数据源中应用
我们上面已经提到,当大量从数据源中读取字符串,其中肯定有重复的,那么我们使用Flyweight模式可以提高效率,以唱片CD为例,在一个XML文件中,存放了多个CD的资料.

每个CD有三个字段:
1.出片日期(year)
2.歌唱者姓名等信息(artist)
3.唱片曲目
(title)

其中,歌唱者姓名有可能重复,也就是说,可能有同一个演唱者的多个不同时期
不同曲目的CD.我们将"歌唱者姓名"作为可共享的ConcreteFlyweight.其他两个字段作为UnsharedConcreteFlyweight.

首先看看数据源XML文件的内容:


<?xml
version="1.0"?>
<collection>

<cd>
<title>Another
Green World</title>
<year>1978</year>
<artist>Eno,
Brian</artist>
</cd>

<cd>
<title>Greatest
Hits</title>
<year>1950</year>
<artist>Holiday,
Billie</artist>
</cd>

<cd>
<title>Taking
Tiger Mountain (by
strategy)</title>
<year>1977</year>
<artist>Eno,
Brian</artist>
</cd>

…….


</collection>
 


虽然上面举例CD只有3张,CD可看成是大量重复的小类,因为其中成分只有三个字段,而且有重复的(歌唱者姓名).

CD就是类似上面接口
Flyweight:


public class CD {

  private String
title;
  private int year;
  private Artist artist;

  public String
getTitle() {  return title; }
  public int getYear() {    return
year;  }
  public Artist getArtist() {    return artist;  }

  public
void setTitle(String t){    title = t;}
  public void setYear(int y){year =
y;}
  public void setArtist(Artist a){artist =
a;}

}
 

将"歌唱者姓名"作为可共享的ConcreteFlyweight:

public class
Artist {

  //内部状态
  private String name;

  // note that Artist
is immutable.
  String getName(){return name;}

  Artist(String
n){
    name = n;
  }

}
 

再看看Flyweight
factory,专门用来制造上面的可共享的ConcreteFlyweight:Artist

public class ArtistFactory
{

  Hashtable pool = new Hashtable();

  Artist getArtist(String
key){

    Artist result;
    result =
(Artist)pool.get(key);
    ////产生新的Artist
    if(result == null)
{
      result = new
Artist(key);
      pool.put(key,result);
      
    }
    return
result;
 
}

}
 

当你有几千张甚至更多CD时,Flyweight模式将节省更多空间,共享的flyweight越多,空间节省也就越大.

Bridge定义
:
将抽象和行为划分开来,各自独立,但能动态的结合.

为什么使用?
通常,当一个抽象类或接口有多个具体实现(concrete
subclass),这些concrete之间关系可能有以下两种:
1. 这多个具体实现之间恰好是并列的,如前面举例,打桩,有两个concrete
class:方形桩和圆形桩;这两个形状上的桩是并列的,没有概念上的重复,那么我们只要使用继承就可以了.

2.实际应用上,常常有可能在这多个concrete
class之间有概念上重叠.那么需要我们把抽象共同部分和行为共同部分各自独立开来,原来是准备放在一个接口里,现在需要设计两个接口,分别放置抽象和行为.

例如,一杯咖啡为例,有中杯和大杯之分,同时还有加奶
不加奶之分. 如果用单纯的继承,这四个具体实现(中杯 大杯 加奶 不加奶)之间有概念重叠,因为有中杯加奶,也有中杯不加奶,
如果再在中杯这一层再实现两个继承,很显然混乱,扩展性极差.那我们使用Bridge模式来实现它.

如何实现?
以上面提到的咖啡 为例.
我们原来打算只设计一个接口(抽象类),使用Bridge模式后,我们需要将抽象和行为分开,加奶和不加奶属于行为,我们将它们抽象成一个专门的行为接口.

先看看抽象部分的接口代码:

public
abstract class Coffee
{
  CoffeeImp coffeeImp;

  public void
setCoffeeImp() {
    this.CoffeeImp =
CoffeeImpSingleton.getTheCoffeImp();
  }

  public CoffeeImp
getCoffeeImp() {return this.CoffeeImp;}

  public abstract void
pourCoffee();
}
 

其中CoffeeImp 是加不加奶的行为接口,看其代码如下:

public
abstract class CoffeeImp
{
  public abstract void
pourCoffeeImp();
}
 

现在我们有了两个抽象类,下面我们分别对其进行继承,实现concrete
class:

//中杯
public class MediumCoffee extends Coffee
{
  public
MediumCoffee() {setCoffeeImp();}

  public void
pourCoffee()
  {
    CoffeeImp coffeeImp =
this.getCoffeeImp();
    //我们以重复次数来说明是冲中杯还是大杯 ,重复2次是中杯
    for (int i = 0;
i < 2;
i++)
    {

      coffeeImp.pourCoffeeImp();
    }
  
  }
}

//大杯
public
class SuperSizeCoffee extends Coffee
{
  public SuperSizeCoffee()
{setCoffeeImp();}

  public void pourCoffee()
  {
    CoffeeImp
coffeeImp = this.getCoffeeImp();
    //我们以重复次数来说明是冲中杯还是大杯 ,重复5次是大杯
    for
(int i = 0; i < 5;
i++)
    {

      coffeeImp.pourCoffeeImp();
    }
  
  }
}
 

上面分别是中杯和大杯的具体实现.下面再对行为CoffeeImp进行继承:

//加奶
public
class MilkCoffeeImp extends CoffeeImp
{
  MilkCoffeeImp()
{}

  public void
pourCoffeeImp()
  {
    System.out.println("加了美味的牛奶");
  }
}

//不加奶
public
class FragrantCoffeeImp extends CoffeeImp
{
  FragrantCoffeeImp()
{}

  public void
pourCoffeeImp()
  {
    System.out.println("什么也没加,清香");
  }
}
 

Bridge模式的基本框架我们已经搭好了,别忘记定义中还有一句:动态结合,我们现在可以喝到至少四种咖啡:
1.中杯加奶
2.中杯不加奶
3.大杯加奶
4.大杯不加奶

看看是如何动态结合的,在使用之前,我们做个准备工作,设计一个单态类(Singleton)用来hold当前的CoffeeImp:

public
class CoffeeImpSingleton
{
  private static CoffeeImp
coffeeImp;

  public CoffeeImpSingleton(CoffeeImp coffeeImpIn)
  
{this.coffeeImp = coffeeImpIn;}

  public static CoffeeImp
getTheCoffeeImp()
  {
    return coffeeImp;
  }
}
 

看看中杯加奶
和大杯加奶 是怎么出来的:

//拿出牛奶
CoffeeImpSingleton coffeeImpSingleton = new
CoffeeImpSingleton(new MilkCoffeeImp());

//中杯加奶
MediumCoffee
mediumCoffee = new
MediumCoffee();
mediumCoffee.pourCoffee();

//大杯加奶
SuperSizeCoffee
superSizeCoffee = new
SuperSizeCoffee();
superSizeCoffee.pourCoffee();

注意:
Bridge模式的执行类如CoffeeImp和Coffee是一对一的关系,
正确创建CoffeeImp是该模式的关键,

Bridge模式在EJB中的应用
EJB中有一个Data Access Object
(DAO)模式,这是将商业逻辑和具体数据资源分开的,因为不同的数据库有不同的数据库操作.将操作不同数据库的行为独立抽象成一个行为接口DAO.如下:

1.Business
Object
(类似Coffee)

实现一些抽象的商业操作:如寻找一个用户下所有的订单

涉及数据库操作都使用DAOImplementor.

2.Data
Access Object (类似CoffeeImp)

一些抽象的对数据库资源操作

3.DAOImplementor
如OrderDAOCS, OrderDAOOracle, OrderDAOSybase(类似MilkCoffeeImp
FragrantCoffeeImp)

具体的数据库操作,如"INSERT INTO "等语句,OrderDAOOracle是Oracle
OrderDAOSybase是Sybase数据库.

4.数据库 (Cloudscape, Oracle, or Sybase database
via JDBC API)


Decorator常被翻译成"装饰",我觉得翻译成"油漆工"更形象点,油漆工(decorator)是用来刷油漆的,那么被刷油漆的对象我们称decoratee.这两种实体在Decorator模式中是必须的.

Decorator定义:
动态给一个对象添加一些额外的职责,就象在墙上刷油漆.使用Decorator模式相比用生成子类方式达到功能的扩充显得更为灵活.

为什么使用Decorator?
我们通常可以使用继承来实现功能的拓展,如果这些需要拓展的功能的种类很繁多,那么势必生成很多子类,增加系统的复杂性,同时,使用继承实现功能拓展,我们必须可预见这些拓展功能,这些功能是编译时就确定了,是静态的.

使用Decorator的理由是:这些功能需要由用户动态决定加入的方式和时机.Decorator提供了"即插即用"的方法,在运行期间决定何时增加何种功能.

如何使用?
举Adapter中的打桩示例,在Adapter中有两种类:方形桩
圆形桩,Adapter模式展示如何综合使用这两个类,在Decorator模式中,我们是要在打桩时增加一些额外功能,比如,挖坑
在桩上钉木板等,不关心如何使用两个不相关的类.

我们先建立一个接口:

public inte***ce Work
{

  public void
insert();

}
 

接口Work有一个具体实现:插入方形桩或圆形桩,这两个区别对Decorator是无所谓.我们以插入方形桩为例:

public
class SquarePeg implements Work{
  public void
insert(){
    System.out.println("方形桩插入");
  }


}
 

现在有一个应用:需要在桩打入前,挖坑,在打入后,在桩上钉木板,这些额外的功能是动态,可能随意增加调整修改,比如,可能又需要在打桩之后钉架子(只是比喻).

那么我们使用Decorator模式,这里方形桩SquarePeg是decoratee(被刷油漆者),我们需要在decoratee上刷些"油漆",这些油漆就是那些额外的功能.

public
class Decorator implements Work{

  private Work
work;
  //额外增加的功能被打包在这个List中
  private ArrayList others = new ArrayList();


  //在构造器中使用组合new方式,引入Work对象;
  public Decorator(Work
work)
  {
    this.work=work;
  

    others.add("挖坑");

    others.add("钉木板");
  }

  public
void insert(){

    newMethod();
  }



  
  //在新方法中,我们在insert之前增加其他方法,这里次序先后是用户灵活指定的   
  public
void
newMethod()
  {
    otherMethod();
    work.insert();


  }


  public void otherMethod()
  {
    ListIterator listIterator =
others.listIterator();
    while
(listIterator.hasNext())
    {
      System.out.println(((String)(listIterator.next()))
+ " 正在进行");
    }

  }


}
 

在上例中,我们把挖坑和钉木板都排在了打桩insert前面,这里只是举例说明额外功能次序可以任意安排.

好了,Decorator模式出来了,我们看如何调用:

Work
squarePeg = new SquarePeg();
Work decorator = new
Decorator(squarePeg);
decorator.insert();


Decorator模式至此完成.

如果你细心,会发现,上面调用类似我们读取文件时的调用:

FileReader fr
= new FileReader(filename);
BufferedReader br = new
BufferedReader(fr);

实际上Java 的I/O
API就是使用Decorator实现的,I/O变种很多,如果都采取继承方法,将会产生很多子类,显然相当繁琐.

Jive中的Decorator实现
在论坛系统中,有些特别的字是不能出现在论坛中如"打倒XXX",我们需要过滤这些"反动"的字体.不让他们出现或者高亮度显示.

在IBM
Java专栏中专门谈Jive的文章中,有谈及Jive中ForumMessageFilter.java使用了Decorator模式,其实,该程序并没有真正使用Decorator,而是提示说:针对特别论坛可以设计额外增加的过滤功能,那么就可以重组ForumMessageFilter作为Decorator模式了.

所以,我们在分辨是否真正是Decorator模式,以及会真正使用Decorator模式,一定要把握好Decorator模式的定义,以及其中参与的角色(Decoratee
和Decorator).

Composite定义:
将对象以树形结构组织起来,以达成“部分-整体”
的层次结构,使得客户端对单个对象和组合对象的使用具有一致性.

Composite比较容易理解,想到Composite就应该想到树形结构图。组合体内这些对象都有共同接口,当组合体一个对象的方法被调用执行时,Composite将遍历(Iterator)整个树形结构,寻找同样包含这个方法的对象并实现调用执行。可以用牵一动百来形容。

所以Composite模式使用到Iterator模式,和Chain
of
Responsibility模式类似。

Composite好处:
1.使客户端调用简单,客户端可以一致的使用组合结构或其中单个对象,用户就不必关系自己处理的是单个对象还是整个组合结构,这就简化了客户端代码。
2.更容易在组合体内加入对象部件.
客户端不必因为加入了新的对象部件而更改代码。

如何使用Composite?
首先定义一个接口或抽象类,这是设计模式通用方式了,其他设计模式对接口内部定义限制不多,Composite却有个规定,那就是要在接口内部定义一个用于访问和管理Composite组合体的对象们(或称部件Component).

下面的代码是以抽象类定义,一般尽量用接口inte***ce,

public
abstract class Equipment
{
  private String name;
  //网络价格
  public
abstract double netPrice();
  //折扣价格
  public abstract double
discountPrice();
  //增加部件方法  
  public boolean add(Equipment equipment) {
return false; }
  //删除部件方法
  public boolean remove(Equipment equipment) {
return false; }
  //注意这里,这里就提供一种用于访问组合体类的部件方法。
  public Iterator iter() {
return null; }
  
  public Equipment(final String name) { this.name=name;
}
}

 

抽象类Equipment就是Component定义,代表着组合体类的对象们,Equipment中定义几个共同的方法。

public
class Disk extends Equipment
{
  public Disk(String name) { super(name);
}
  //定义Disk网络价格为1
  public double netPrice() { return 1.;
}
  //定义了disk折扣价格是0.5 对折。
  public double discountPrice() { return .5;
}
}
 

Disk是组合体内的一个对象,或称一个部件,这个部件是个单独元素(
Primitive)。
还有一种可能是,一个部件也是一个组合体,就是说这个部件下面还有’儿子’,这是树形结构中通常的情况,应该比较容易理解。现在我们先要定义这个组合体:

abstract
class CompositeEquipment extends Equipment
{
  private int i=0;

  //定义一个Vector 用来存放’儿子’
  private Lsit equipment=new
ArrayList();

  public CompositeEquipment(String name) { super(name);
}

  public boolean add(Equipment equipment) {
    
this.equipment.add(equipment);
     return true;
   }

  public
double netPrice()
  {
    double netPrice=0.;
    Iterator
iter=equipment.iterator();
    for(iter.hasNext())
      netPrice+=((Equipment)iter.next()).netPrice();
    return
netPrice;
  }

  public double discountPrice()
  {
    double
discountPrice=0.;
    Iterator
iter=equipment.iterator();
    for(iter.hasNext())
      discountPrice+=((Equipment)iter.next()).discountPrice();
    return
discountPrice;
  }
  

  //注意这里,这里就提供用于访问自己组合体内的部件方法。
  //上面dIsk
之所以没有,是因为Disk是个单独(Primitive)的元素.
  public Iterator
iter()
  {
    return equipment.iterator()
;
  {
  //重载Iterator方法
   public boolean hasNext() { return
i<equipment.size(); }
  //重载Iterator方法
   public Object next()
  
{
    if(hasNext())
       return equipment.elementAt(i++);
    else

        throw new NoSuchElementException();
  
}
  

}
 

上面CompositeEquipment继承了Equipment,同时为自己里面的对象们提供了外部访问的方法,重载了Iterator,Iterator是Java的Collection的一个接口,是Iterator模式的实现.

我们再看看CompositeEquipment的两个具体类:盘盒Chassis和箱子Cabinet,箱子里面可以放很多东西,如底板,电源盒,硬盘盒等;盘盒里面可以放一些小设备,如硬盘
软驱等。无疑这两个都是属于组合体性质的。

public class Chassi* **tends
CompositeEquipment
{
   public Chassis(String name) { super(name); }
  
public double netPrice() { return 1.+super.netPrice(); }
   public double
discountPrice() { return .5+super.discountPrice(); }
}

public class
Cabinet extends CompositeEquipment
{
   public Cabinet(String name) {
super(name); }
   public double netPrice() { return 1.+super.netPrice();
}
   public double discountPrice() { return .5+super.discountPrice();
}
}
 

至此我们完成了整个Composite模式的架构。

我们可以看看客户端调用Composote代码:

Cabinet
cabinet=new Cabinet("Tower");

Chassis chassis=new Chassis("PC
Chassis");
//将PC Chassis装到Tower中
(将盘盒装到箱子里)
cabinet.add(chassis);
//将一个10GB的硬盘装到 PC Chassis
(将硬盘装到盘盒里)
chassis.add(new Disk("10 GB"));

//调用
netPrice()方法;
System.out.println("netPrice="+cabinet.netPrice());
System.out.println("discountPrice="+cabinet.discountPrice());

上面调用的方法netPrice()或discountPrice(),实际上Composite使用Iterator遍历了整个树形结构,寻找同样包含这个方法的对象并实现调用执行.

Composite是个很巧妙体现智慧的模式,在实际应用中,如果碰到树形结构,我们就可以尝试是否可以使用这个模式。

以论坛为例,一个版(forum)中有很多帖子(message),这些帖子有原始贴,有对原始贴的回应贴,是个典型的树形结构,那么当然可以使用Composite模式,那么我们进入Jive中看看,是如何实现的.

Jive解剖
在Jive中
ForumThread是ForumMessages的容器container(组合体).也就是说,ForumThread类似我们上例中的
CompositeEquipment.它和messages的关系如图:
[thread]
   |- [message]
   |-
[message]
      |- [message]
      |- [message]
         |- [message]


我们在ForumThread看到如下代码:

public inte***ce ForumThread {
   ….

   public void addMessage(ForumMessage parentMessage, ForumMessage
newMessage)
         throws UnauthorizedException;

   public void
deleteMessage(ForumMessage message)
         throws
UnauthorizedException;

  
   public Iterator messages();
      ….


}
 

类似CompositeEquipment, 提供用于访问自己组合体内的部件方法: 增加 删除
遍历.

结合我的其他模式中对Jive的分析,我们已经基本大体理解了Jive论坛体系的框架,如果你之前不理解设计模式,而直接去看Jive源代码,你肯定无法看懂。

:)

定义:
将两个不兼容的类纠合在一起使用,属于结构型模式,需要有Adaptee(被适配者)和Adaptor(适配器)两个身份.

为何使用?
我们经常碰到要将两个没有关系的类组合在一起使用,第一解决方案是:修改各自类的接口,但是如果我们没有源代码,或者,我们不愿意为了一个应用而修改各自的接口。
怎么办?


使用Adapter,在这两种接口之间创建一个混合接口(混血儿).

如何使用?
实现Adapter方式,其实"think in
Java"的"类再生"一节中已经提到,有两种方式:组合(composition)和继承(inheritance).


假设我们要打桩,有两种类:方形桩
圆形桩.
public class SquarePeg{
  public void insert(String
str){
    System.out.println("SquarePeg
insert():"+str);
  }

}

public class RoundPeg{
  public void
insertIntohole(String msg){
    System.out.println("RoundPeg
insertIntoHole():"+msg);
}
}

现在有一个应用,需要既打方形桩,又打圆形桩.那么我们需要将这两个没有关系的类综合应用.假设RoundPeg我们没有源代码,或源代码我们不想修改,那么我们使用Adapter来实现这个应用:

public
class PegAdapter extends SquarePeg{

  private RoundPeg
roundPeg;

  public PegAdapter(RoundPeg
peg)(this.roundPeg=peg;)

  public void insert(String str){
roundPeg.insertIntoHole(str);}

}

在上面代码中,RoundPeg属于Adaptee,是被适配者.PegAdapter是Adapter,将Adaptee(被适配者RoundPeg)和Target(目标SquarePeg)进行适配.实际上这是将组合方法(composition)和继承(inheritance)方法综合运用.

PegAdapter首先继承SquarePeg,然后使用new的组合生成对象方式,生成RoundPeg的对象roundPeg,再重载父类insert()方法。从这里,你也了解使用new生成对象和使用extends继承生成对象的不同,前者无需对原来的类修改,甚至无需要知道其内部结构和源代码.

如果你有些Java使用的经验,已经发现,这种模式经常使用。

进一步使用
上面的PegAdapter是继承了SquarePeg,如果我们需要两边继承,即继承SquarePeg
又继承RoundPeg,因为Java中不允许多继承,但是我们可以实现(implements)两个接口(inte***ce)

public
inte***ce IRoundPeg{
  public void insertIntoHole(String
msg);

}

public inte***ce ISquarePeg{
  public void
insert(String str);

}

下面是新的RoundPeg 和SquarePeg,
除了实现接口这一区别,和上面的没什么区别。
public class SquarePeg implements
ISquarePeg{
  public void insert(String
str){
    System.out.println("SquarePeg
insert():"+str);
  }

}

public class RoundPeg implements
IRoundPeg{
  public void insertIntohole(String
msg){
    System.out.println("RoundPeg
insertIntoHole():"+msg);
  }
}

下面是新的PegAdapter,叫做two-way
adapter:

public class PegAdapter implements
IRoundPeg,ISquarePeg{

  private RoundPeg roundPeg;
  private SquarePeg
squarePeg;

  // 构造方法
  public PegAdapter(RoundPeg
peg){this.roundPeg=peg;}
  // 构造方法
  public PegAdapter(SquarePeg
peg)(this.squarePeg=peg;)

  public void insert(String str){
roundPeg.insertIntoHole(str);}

}

还有一种叫Pluggable
Adapters,可以动态的获取几个adapters中一个。使用Reflection技术,可以动态的发现类中的Public方法。

因此,Forum中各种操作权限是和ForumPermissions定义的用户级别有关系的,作为接口Forum的实现:ForumProxy正是将这种对应关系联系起来.比如,修改Forum的名称,只有论坛管理者或系统管理者可以修改,代码如下:

public
class ForumProxy implements Forum {

private ForumPermissions
permissions;
private Forum forum;
this.authorization = authorization;


public ForumProxy(Forum forum, Authorization
authorization,
ForumPermissions permissions)
{
this.forum =
forum;
this.authorization = authorization;
this.permissions =
permissions;
}

…..

public void setName(String name) throws
UnauthorizedException,
ForumAlreadyExist***ception
{
  //只有是系统或论坛管理者才可以修改名称
  if
(permissions.isSystemOrForumAdmin())
{
    forum.setName(name);
  }
  else {
    throw new
UnauthorizedException();
  }
}



}
 

而DbForum才是接口Forum的真正实现,以修改论坛名称为例:

public
class DbForum implements Forum, Cacheable {


public void
setName(String name) throws ForumAlreadyExist***ception
{

  ….

  this.name = name;
  //这里真正将新名称保存到数据库中

  saveToDb();

  ….
}





}
 

凡是涉及到对论坛名称修改这一事件,其他程序都首先得和ForumProxy打交道,由ForumProxy决定是否有权限做某一样事情,ForumProxy是个名副其实的"网关","安全代理系统".

在平时应用中,无可避免总要涉及到系统的授权或安全体系,不管你有无意识的使用Proxy,实际你已经在使用Proxy了.

我们继续结合Jive谈入深一点,下面要涉及到工厂模式了,如果你不了解工厂模式,请看我的另外一篇文章:设计模式之Factory

我们已经知道,使用Forum需要通过ForumProxy,Jive中创建一个Forum是使用Factory模式,有一个总的抽象类ForumFactory,在这个抽象类中,调用ForumFactory是通过getInstance()方法实现,这里使用了Singleton(也是设计模式之一,由于介绍文章很多,我就不写了,看这里),getInstance()返回的是ForumFactoryProxy.

为什么不返回ForumFactory,而返回ForumFactory的实现ForumFactoryProxy?
原因是明显的,需要通过代理确定是否有权限创建forum.

在ForumFactoryProxy中我们看到代码如下:

public
class ForumFactoryProxy extends ForumFactory {

  protected ForumFactory
factory;
  protected Authorization authorization;
  protected
ForumPermissions permissions;

  public ForumFactoryProxy(Authorization
authorization, ForumFactory factory,
  ForumPermissions
permissions)
  {
    this.factory = factory;
    this.authorization =
authorization;
    this.permissions = permissions;
  }

  public
Forum createForum(String name, String description)
      throws
UnauthorizedException,
ForumAlreadyExist***ception
  {
    //只有系统管理者才可以创建forum
    if
(permissions.get(ForumPermissions.SYSTEM_ADMIN)) {
      Forum newForum =
factory.createForum(name, description);
      return new ForumProxy(newForum,
authorization, permissions);
    }
    else {
      throw new
UnauthorizedException();
  }
}
 

方法createForum返回的也是ForumProxy,
Proxy就象一道墙,其他程序只能和Proxy交互操作.

注意到这里有两个Proxy:ForumProxy和ForumFactoryProxy.
代表两个不同的职责:使用Forum和创建Forum;
至于为什么将使用对象和创建对象分开,这也是为什么使用Factory模式的原因所在:是为了"封装"
"分派";换句话说,尽可能功能单一化,方便维护修改.

Jive论坛系统中其他如帖子的创建和使用,都是按照Forum这个思路而来的.

以上我们讨论了如何使用Proxy进行授权机制的访问,Proxy还可以对用户隐藏另外一种称为copy-on-write的优化方式.拷贝一个庞大而复杂的对象是一个开销很大的操作,如果拷贝过程中,没有对原来的对象有所修改,那么这样的拷贝开销就没有必要.用代理延迟这一拷贝过程.

比如:我们有一个很大的Collection,具体如hashtable,有很多客户端会并发同时访问它.其中一个特别的客户端要进行连续的数据获取,此时要求其他客户端不能再向hashtable中增加或删除
东东.

最直接的解决方案是:使用collection的lock,让这特别的客户端获得这个lock,进行连续的数据获取,然后再释放lock.
public
void foFetches(Hashtable ht){
  synchronized(ht){
    //具体的连续数据获取动作..

  }


}

但是这一办法可能锁住Collection会很长时间,这段时间,其他客户端就不能访问该Collection了.

第二个解决方案是clone这个Collection,然后让连续的数据获取针对clone出来的那个Collection操作.这个方案前提是,这个Collection是可clone的,而且必须有提供深度clone的方法.Hashtable就提供了对自己的clone方法,但不是Key和value对象的clone,关于Clone含义可以参考专门文章.
public
void foFetches(Hashtable ht){

  Hashttable
newht=(Hashtable)ht.clone();

}

问题又来了,由于是针对clone出来的对象操作,如果原来的母体被其他客户端操作修改了,
那么对clone出来的对象操作就没有意义了.

最后解决方案:我们可以等其他客户端修改完成后再进行clone,也就是说,这个特别的客户端先通过调用一个叫clone的方法来进行一系列数据获取操作.但实际上没有真正的进行对象拷贝,直至有其他客户端修改了这个对象Collection.

使用Proxy实现这个方案.这就是copy-on-write操作.

Proxy应用范围很广,现在流行的分布计算方式RMI和Corba等都是Proxy模式的应用.