2010-01-03
2009-02-08
I love Mockups!!!!!!!!!!!
http://www.balsamiq.com/products/mockups
中文介绍:
http://www.yining.org/2009/01/09/balsamiq-mockups-review/
2008-09-28
Head First Pattern之代理模式
远程代理
虚拟代理
虚拟代理举的是一个Swing的例子。
我是这么理解的:一个对象的创建非常耗时,通过代理对象去调用,在真实对象创建前,返回一个假的调用,等真实对象创建好了,这时候返回给客户端的就是一个真实对象的相应方法调用。
也就是延迟加载的问题,Swing例子中,要显示一个Icon,但是要通过网络加载一个图片,在图片通过网络加载成功前,先显示一个“加载中,请稍候...”(如果是真实对象的调用,应该显示一个图片),在代理对象中通过后台线程去加载图片,加载完了后,再偷偷的把“加载中,请稍候...”的字样偷偷换成加载成功后的图片。
没想到这也算代理模式的一种应用场景。以前有这么在Swing中用过,需要从数据库中查找数据,但是比较耗时,就先显示“加载数据中,请稍候...”,等加载完了,再在JTable中显示出来。如果用代理模式的方式来思考,好像比较的好吧。。
同样在jsp页面里,通过ajax来加载数据好像也是这样的道理,数据没加载之前就是“加载中...”,加载完了再通过innerHTML来改变显示,也是同样的延迟加载问题。
如果用代理模式的方式来考虑,可以定义一个JavaScript类(这个类其实是个代理),这个类有个方法要显示一些从Server取出的数据,但是调用显示方法时,后台数据还没有加载,就先显示加载中请稍候之类的文本,这时候通过ajax从Server取数据(创建真实对象),取出来之后在回调函数中更新显示HTML元素的innerHTML。跟那个Swing的例子一模一样吧。不过好像JavaScript中好像没有谁会定义接口、实现、代理对象吧,但是思路其实是一样的。
不知道这样理解代理模式,算不算曲解。。。
JDK动态代理
jdk里的动态代理支持,主要是通过java.lang.reflect包中Proxy、InvocationHandler等几个类来实现的。具体如何实现可参考JDK中文文档。
使用场合:
好像在在一本Hibernate的书上,对数据库Connection的close方法调用,用动态代理的方式来拦截,并不真正关闭连接,而是返回到数据库连接池中。
在Spring中的拦截貌似有些是用动态代理实现的?不过动态代理使用时要基于接口,但是Spring是使用动态生成字节码的方式?对Spring内部实现机制不熟。。不敢妄自猜测。。等有时间好好研究再来说明。。
和Decorator的比较
多线程环境下的单例模式实现
----Head First Pattern之单例模式
单例模式我想大家都比较熟悉,就是在JVM运行期间一个类只有一个实例,任何时候都是取得同一个实例,也就是一个全局变量了。
单例模式分懒汉式和饿汉式,但是懒汉式的单例在多线程环境下会有同步的问题,下面详细介绍了用3中方法来解决此问题。
单例模式具有以下几个特点:
1.JVM运行期间有且只有一个实例
2.构造函数是私有的
3.通过一个静态工厂方法来获得唯一的实例
4.累内部有一个私有静态实例,通过静态工厂方法创建后,每次再调用静态工厂方法,返回的都是同一个实例
饿汉式:
public class Singleton{
private static Singleton uniqueInstance = new Singleton();
// 其他实例变量
private Singleton(){}
public static Singleton getInstance(){
return uniqueInstance;
}
// 其他方法
}
懒汉式:
public class Singleton{
private static Singleton uniqueInstance;
// 其他实例变量
private Singleton(){}
public static Singleton getInstance(){
if(uniqueInstance == null){
uniqueInstance = new Signleton();
}
return uniqueInstance;
}
// 其他方法
}
多线程环境下的单例模式:
上面的代码就是最基本的单例模式示例代码。但是懒汉式单例有一个问题,因为要保证有且仅有一个实例,如果在多线程环境下调用Singleton.getInstance(),就可能会有多个实例!为了解决多线程访问的问题,有3种解决方法供选择:
1.静态工厂方法加同步关键字,这种方法是在对性能要求不高的情况下采用。
public class Singleton{
private static Singleton uniqueInstance;
// 其他实例变量
private Singleton(){}
public static synchronised Singleton getInstance(){
if(uniqueInstance == null){
uniqueInstance = new Signleton();
}
return uniqueInstance;
}
// 其他方法
}
2.始终用饿汉式单例
public class Singleton{
private static Singleton uniqueInstance = new Singleton();
// 其他实例变量
private Singleton(){}
public static Singleton getInstance(){
return uniqueInstance;
}
// 其他方法
}
饿汉式的方法,会依赖于JVM在加载类的时候,就创建唯一的实例。在每个线程访问getInstance方法前,唯一实例已经被创建。
3.用双检查锁来减少懒汉式中静态方法getInstance的同步开销
对public static synchronised Singleton getInstance()的每次调用,都需要同步,而双检查锁的方式只是在第一次创建实例时同步,其他时候并不需要同步。
public class Singleton{
private volatile static Singleton uniqueInstance;
private Singleton(){}
public static Singleton getInstance(){
if(uniqueInstance == null){
synchronised(Singleton.class){
if(uniqueInstance == null){
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
如果调用时实例为null,则进入同步区块,此时再进行判断,如果还为null,就创建唯一的实例。有可能在一个线程在 if(uniqueInstance == null) 后进入同步区块前,另一个线程恰好已经创建成功并从同步区块中出来,这就需要进入同步区块后,再做uniqueInstance是否为null的判断。
同时uniqueInstance需要加volatile关键字,保证在创建单例实例时,多个线程能正确处理uniqueInstance变量。
注意:
双检查锁的方式在Java1.4及1.4以前版本不能工作!!因此双检查锁只能在Java 5及以上版本才可以使用。
记得Effictive Java中也提到过双检查锁,也说不能在Java1.4中使用。
原因是Java 1.4及以前的JVM中对volatile关键字的实现允许对双检查锁不合适的同步。(谁能帮我再深入解释下?)原文是:
Unfortunately, in Java version 1.4 and earlier, many JVMs contain implementations of the volatile keyword that allow improper synchronization for double-checked locking. If you must use a JVM other than Java 5, consider other methods of implementing your Singleton.
移动硬盘与适配器模式
----Head
First Pattern之适配器模式
很多人都有移动硬盘,它比U盘容量大的多,体积也小,携带方便,用来拷资料、备份都很不错。并且硬盘价格越来越便宜,120G的移动硬盘现在才300多。
其实移动硬盘里面就是一块普通的笔记本硬盘,还有一个IDE口(或SATA口,看硬盘的类型了,现在一般都是SATA口即串口,IDE口就是并口)和USB口的转换卡,用USB线和电脑上的USB口连接起来就行了。
我手头有一个40G的IDE口笔记本硬盘,想在PC电脑上用,但又不想把电脑拆开插主板的IDE线上。看到电脑上还有很多空闲的USB口,就想能不能让笔记本硬盘的IDE口连到电脑的USB口?直接连肯定不行,接口不匹配啊。就去电子市场逛了一个下午,发现有一种转接卡,一头可以插IDE口,一头是USB口,能连USB线,USB线就可以连电脑的USB口了。我买了转接卡回到家,把转接卡一头插在笔记本硬盘的IDE口上,另一头连上USB线,再把USB线连到电脑的USB口上。大功告成,现在我的笔记本硬盘就变成移动硬盘了。转接卡就是连接IDE口和USB口的适配器。这就是适配器模式。
下面看适配器的UML类图:
这个类图有点问题,其实Adaptee应该是一个接口,还有个实现这个接口的ConcreteAdaptee,而Adapter是目标接口,ConcreteAdapter必须实现Adapter,持有一个Adaptee。移动硬盘和适配器模式对应关系如下:
Adapter:
USB接口
Adaptee:笔记本硬盘的IDE接口
ConcreteAdapter:转接卡
总而言之,适配器模式就是把一个接口Adaptee,适配成目标接口Adapter。
某些时候适配器和装饰模式有点相同之处,但是两者本质是不一样的。
Adapter是把所包装的接口,转换为另一个接口。而Decorator是给所包装的接口,添加新的行为或方法。
Servlet与模板方法模式
----Head First Pattern之模板方法模式
这年头大家都用struts,或者其他MVC框架,很少有人直接用Servlet了吧,但是相信大部分人都应该知道怎么写Servlet的吧,继承HttpServlet类,覆盖里面的doGet、doPost方法即可,大部分情况下,我们都是对GET和POST一样处理,一般也就这么写了:
public MyServlet extends HttpServlet{
public void doGet(HttpServletRequest request, HttpServletResponse response){
// 处理
}
public void doPost()HttpServletRequest request, HttpServletResponse response){
// 不关心get、post请求,因此post处理直接调用get处理
doGet(request, response);
}
}
Servlet只要在web.xml中部署好之后,就可以处理浏览器的请求了。上面代码可以看出来,doGet方法处理浏览器的GET请求,doPost处理POST请求。
Servlet是由Tomcat之类的servlet容器来调用处理浏览器请求的,并需要集成基类HttpServlet,如果大家查看HttpServlet源码的时候,就会发现,其实里面有一个
protected void service(HttpServletRequest req, HttpServletResponse resp)
方法,servlet容器实际调用的是service方法,service方法的实现就是根据HTTP请求的类型(GET、POST,还是其他),将处理委派给doGet、doPost等方法,由这些子类的方法来最终处理浏览器的请求。
由此可以看出,HttpServlet定义了一个处理的框架或者说模板,实现Servlet只需继承HttpServlet并实现doGet、doPost等方法即可。
是引出模板方法模式定义的时候了,模板方法模式:在一个方法中定义一个算法的骨架,将某些步骤推迟到子类中实现。模板方法允许子类重新定义算法的某些步骤,而不改变算法的结构。
简单UML类图如下:
public abstract Template{
public void final templateMethod(){
step1();
step2();
hook();
}
public abstract void step1();
public abstract void step2();
public void hook(){}
}
这个抽象类,定义了一个算法的骨架,需要step1、step2,都是抽象方法,需要子类来实现。而templateMethod是final的,即不允许子类覆盖。其中定义了方法的步骤,step1、step2。
如下为具体实现:
public ConcreteTemplate extends Template{
public void step1(){System.out.println("step1");}
public void step2(){System.out.println("step2");}
public void hook(){System.out.println("hook");}
public static void main(String[] args){
Template temp = new ConcreteTemplate();
temp.templateMethod();
}
}
可以看到其中加入了一个hook方法,即钩子方法。hook方法在抽象类中的实现为空,是留给子类做一些可选的操作。如果某个子类需要一些特殊额外的操作,则可以实现hook方法,当然也可以完全不用理会,因为hook在抽象类中只是空方法而已。
其他扩展:
1.可以定义多个hook方法
2.hook方法可以定义一个返回为boolean的方法,有子类来决定是否调用hook方法。
eg:抽象类的templateMethod可以这样实现:
public void final templateMethod(){
step1();
step2();
if(allowHook())
hook();
}
public boolean allowHook(){return true;}
public void hook(){}
子类中可以覆盖allowHook,以决定是否调用hook方法。
3.抽象类定义的步骤,可以有默认实现,而非全是abstract方法。HttpServlet中已经有doGet、doPost等方法的默认实现,大家可以参考其源码实现。
更好的一个模板方法的例子是Applet,实现自己的Applet,必须继承自Applet,里面实现init、start、stop、destroy等方法即可。这些方法都是有默认实现的,如果看源码,可以发现默认实现其实都是空。
了解模板方法模式之后,大家可能意识到Servlet并且完全按照模板方法定义的那样,而是有一些区别,比如提供默认doGet、doPost的实现等。这都说明,在实际编程中,并非生搬硬套设计模式,而是根据实际,会做些变动或变形,但其本质不变。
不能为了模式而模式。那究竟什么时候需要设计模式呢?个人理解,当你发现你的类有问题的时候,比如有重复代码了,感觉有些不对劲了,感觉不适应以后的扩充了,这时候不妨考虑下,是不是该设计模式出场了。。
纯粹个人理解。。有不当之处,请及时拍砖。。
JavaScript正则表达式总结
一、RegExp对象和字面量
RegExp对象构造方式:
var re = new RegExp("cat");
var re = new RegExp("cat","gim");
g:global,表示找到所有的匹配,如果不指定g选项,只匹配第一个
i:不区分大小写
m:匹配多行
字面量方式:
var re = /cat/;
var re = /cat/gi;
两者区别:
非字面量来表达正则表达式时,对元字符转义必须用双重转义,而字面量不用,eg:
var re = /?/; // ?为元字符必须转义
var re = new RegExp('\?'); // 因为RegExp是用字符串来构造,而必须也转义,故得写成 '\?'
二、简单模式
1、元字符
(、[、{、、^、$、|、)、?、*、+、.
2、使用特殊字符
用ascii码和unicode表示字符
t 制表符
n 换行符
r 回车符
f 换页符
a alert字符
e escape字符
cX 与X相对应的控制字符
b 回退字符
v 垂直制表符
0 空字符
以上特殊字符用非字面量方式构造表达式时,要双重转义
3、字符类
简单类:[]来表示单个字符有或的关系,如/[bc]/,匹配b或者c
负向类:^来表示不匹配后面跟着的字符,如/[^bc],/不匹配b或c
范围类:-来表示一个范围,如/[a-z]/,匹配a-z的字母,可以和负向类结合/^0-9/
组合类:以上几种类组合而成的字符类,如/a-z0-9n/,匹配a-z的字母或者0-9的数字或者换行符
类不能嵌套,就是不支持联合类和交叉类,/a-m[p-z]/和/a-m[^b-e]/在JavaScript中是非法的
预定义类:
. = [^nr] 除了换行和回车之外的任何字符
d = [0-9] 数字
D = [^0-9] 非数字
s = [ tnx0Bfr] 空白字符
S = [^ tnx0Bfr] 非空白字符
w = [a-zA-Z_0-9] 单词字符(字母、数字、下划线)
w = [^a-zA-Z_0-9] 非单词字符
4、量词:某个模式出现的次数
简单量词:
? 出现0或1次
* 出现0或多次
+ 出现1或多次(至少1次)
{n} =n次
{n,m} >=n,<=m
{n,} >=n
贪婪量词:先匹配整个字符串,如果不匹配则去掉最后一个字符再匹配,直到没有任何字符。所有简单量词都是贪婪的。
惰性量词:和贪婪量词相反,即先匹配第一个字符,不匹配则匹配第一第二个字符,直到最后整个字符串。所有简单量词后面加?就是惰性的了。
支配量词:只匹配整个字符串一次,不匹配就结束。所有简单量词后面加+就是支配的了。
三、复杂模式
1、分组:用括号来包括一些字符、字符类、量词来使用的。个人理解为可以将字符、字符类、量词等用括号来组成一个单元,可以对这个单元使用量词,同时这个单元是被存储起来的。分组可以嵌套。
2、反向引用:每个分组都是被存储起来的,存储在分组中的匹配值称为反向引用。
使用正则表达式的test、match、search方法之后,可以通过正则表达式的实例变量来访问到反向引用。RegExp.$1,RegExp.$2...来访问反向引用。
可以直接在分组表达式中使用分组。eg:var re = /dog1/,转义1就引用了第一个反向引用
可以在String的replace方法中通过特殊的序列$1、$2来访问反向引用。eg:var sToChange = '1234 5678';var reMatch = /(d{4}) (d4)/;var sNew = sToChange.replace(reMatch,'$2 $1');很轻松的实现单词的顺序转换。
3、候选:用|来表示模式的或关系。
4、非捕获性分组:创建反向引用的分组是捕获性分组,不创建反向引用的分组就是非捕获性分组。eg:(?:bad)
5、前瞻:某个特定的字符分组出现在另一个字符串之前时才捕获,正向前瞻要放在(?=和)之间(不是分组),负向前瞻放在(?!和)之间。eg:/(bed(?=room))/匹配跟在room前面的bed;/(bed(?!room))/匹配不跟在room前的bed。
6、边界:^行开头,$行结尾,b单词边界,B非单词边界。
7、多行模式:m选项打开,能影响^和$,使^匹配开头和换行符后面的位置,$匹配真正的结尾和换行符。