本文对一些比较相似的概念进行了比较。
Adapter Pattern
Adapter顾名思义是把一种类型的接口转换成为另一种类型的接口使得客户端可以按照它喜欢的方式访问。有两种方式可以实现,一种是组合方式,一种是继承方式。
例如我有一个InterfaceA和一个ClassB。Libaray里面提供的是ClassB,而客户端代码希望访问的是InterfaceA的类型。
interface InterfaceA{ String getData(); } class ClassB{ String getRawData() { return "raw"; } }
使用组合实现:
class Adapter implements InterfaceA { private ClassB classB; Adapter(ClassB classB) { this.classB = classB; } public String getData() { return "processed" + classB.getRawData(); } }
使用继承实现:
class Adapter extends ClassB implements InterfaceA { public String getData() { return "processed" + getRawData(); } }
无论哪种方式。现在客户端代码都可以访问ClassB中提供的功能了。
等等,使用一个wrapper包含一个实例,然后再通过该实例去完成wrapper向外暴露的功能,好像Proxy Pattern也是这么做的。
Proxy Pattern
Proxy模式是对于一些关键或者敏感的资源进行一层封装,从而引入一些类似于缓冲,权限认证的功能。
(下面的例子从http://www.jdon.com/designpatterns/designpattern_proxy.htm抄过来的):
Jive的代码中有一个Forum接口,有若干实现类。如DBForum:
public class DbForum implements Forum, Cacheable { ... public void setName(String name) throws ForumAlreadyExistsException { .... this.name = name; //这里真正将新名称保存到数据库中 saveToDb(); .... } ... }
但是真正使用该Forum的时候使用的是ForumProxy这个类
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, ForumAlreadyExistsException { //只有是系统或论坛管理者才可以修改名称 if (permissions.isSystemOrForumAdmin()) { forum.setName(name); } else { throw new UnauthorizedException(); } } ... }
可以注意到ForumProxy和DbForum有着同样的类型:Forum,从而ForumProxy和DbForum有着同样的接口,二者可以互相替换。而使用Proxy的好处就在于加入了权限验证的额外功能。
Proxy也是使用组合方式实现的,但是不同之处在于,Adapter和Adaptee是不同的类型,暴漏的接口也是不同的,但是Proxy和被Proxy的对象的类型和接口都是一样的,一些额外的功能被透明的apply到了被Proxy对象上。
“使用相同的接口”,这让我想起了decrator模式。
Decrator Pattern
其实现方式几乎与proxy模式一模一样,看个例子(可移驾http://en.wikipedia.org/wiki/Decorator_pattern查看详细描述):
// the Window interface interface Window { public void draw(); // draws the Window public String getDescription(); // returns a description of the Window } // implementation of a simple Window without any scrollbars class SimpleWindow implements Window { public void draw() { // draw window } public String getDescription() { return "simple window"; } } // abstract decorator class - note that it implements Window abstract class WindowDecorator implements Window { protected Window decoratedWindow; // the Window being decorated public WindowDecorator (Window decoratedWindow) { this.decoratedWindow = decoratedWindow; } public void draw() { decoratedWindow.draw(); } } // the first concrete decorator which adds vertical scrollbar functionality class VerticalScrollBarDecorator extends WindowDecorator { public VerticalScrollBarDecorator (Window decoratedWindow) { super(decoratedWindow); } public void draw() { decoratedWindow.draw(); drawVerticalScrollBar(); } private void drawVerticalScrollBar() { // draw the vertical scrollbar } public String getDescription() { return decoratedWindow.getDescription() + ", including vertical scrollbars"; } }
和Proxy相比,他们同样有一个共同的接口,同样hold了一个相同接口,不同实现的实例。同样在重写的方法中(setName,draw)中加入了额外的行为(权限验证,绘制scrollbar)。那他们到底有什么不同呢?
其实最大的不同在于其使用意图。确实如此,代码的组织结构就那么几种,但是为了解决的问题确实完全不同的。在这个例子里面。Proxy模式一般只有一层Proxy,这个Proxy的规则是确定的。而Decrator则不然,从上面的代码我们可以看到一个具体的Decrator叫做 VerticalScrollBarDecorator。它给window加上了垂直滚动条的功能。但是我对于window的扩展需求显然不仅仅是添加一个垂直滚动条,比如再添加一个水平滚动条吧:
// the second concrete decorator which adds horizontal scrollbar functionality class HorizontalScrollBarDecorator extends WindowDecorator { public HorizontalScrollBarDecorator (Window decoratedWindow) { super(decoratedWindow); } public void draw() { decoratedWindow.draw(); drawHorizontalScrollBar(); } private void drawHorizontalScrollBar() { // draw the horizontal scrollbar } public String getDescription() { return decoratedWindow.getDescription() + ", including horizontal scrollbars"; } }
这样我就有了两个Decrator,使用每个Decrator装饰完了之后的结果仍然是一个Window,因此不同的Decrator可以一层套一层,从而达到装饰功能的自由组合。可以想象一下这些组合出来的功能如果使用继承实现是个什么样子(组合爆炸)。想象不来的去看上面列的那个原文地址。因此这就是point了。虽然Proxy和Decrator使用的代码结构是类似的。但是Decrator要解决的问题其实是把不同feature放到不同的装饰类中,然后就可以根据需要组装出含有特定feature(集)的实例出来。
Dynamic Proxy
上面看到我们可以在代码里面写死一个Proxy的声明。Java还提供了一种动态生成Proxy类的方法:Dynamic Proxy。本质上就是在运行时动态的生成一个类,这个类可以实现指定的接口,并且可以定义每个方法如何被处理(有点类似ruby的method missing)。举例如下
class MyInvocationHandler implements InvocationHandler { private Object delegate; MyInvocationHandler(Object delegate) { this.delegate = delegate; } public Object invoke(Object o, Method method, Object[] objects) throws Throwable { method.invoke(delegate, objects); return null; } } Foo delegate = new FooImp(); Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(), new Class[] { Foo.class }, new MyInvocationHandler(delegate));
关于Dynamic Proxy的API去http://docs.oracle.com/javase/1.4.2/docs/api/java/lang/reflect/Proxy.html查看具体的说明(如果不清楚的话)。从代码里可以看到,新生成的这个实例f的行为完全代理给了delegate这个实例变量。假设写成非动态的版本就是:
interface Foo { void method1(); Object method2(); } class FooProxy implements Foo { private Foo delegate; FooProxy(Foo delegate) { this.delegate = delegate; } void method1() { delegate.method1(); } Object method2() { return delegate.method2() } }
这完全就是一个透明代理,如果需要做点什么额外的工作,只需要在“method.invoke(delegate, objects);”前后加点什么就可以了。所以事实上Dynamic Proxy并不是完全为实现Proxy而提供的,它提供的只是一种动态组织代码的方式。既然刚才提到Proxy和Decrator在代码结构上其实是一样的,那么使用动态代理来实现东带Decrator应该也是可以的(尚未尝试)。
Dynamic Proxy的一个重要使用场景是AOP。AOP做的事情是在不改变对象接口的情况下,动态的加入一些新的行为。也就是动态的生成一个Proxy类包裹原来定义好的类,然后在新的Proxy类中添加额外的行为,比如logging和transaction。这样听起来从功能的角度来说更像是一个decrator模式,而不是proxy模式了。所以AOP从某种(一定要加上“某种”二字。因为AOP本身实现的方法也不止一种)角度可以理解为使用Dyanmic “Proxy”技术实现的 “Decrator”模式。