代理模式与装饰器模式有何区别?

本文有一些争议,谈论的是设计模式中的代理模式装饰器模式的区别,但笔者是非常赞同文章的观点的,有种豁然开朗的感觉。

以下是原文


代理模式与装饰器模式有何区别?

我想有必要对此问题谈一下我的个人理解,若有误导的之处,还请大家指正!

  1. 代理模式(Proxy 模式)可理解为:我想做,但不能做,我需要有一个能干的人来帮我做。
  2. 装饰器模式(Decorator 模式)可理解为:我想做,但不能做,我需要有各类特长的人来帮我做,但我有时只需要一个人,有时又需要很多人。

它们的区别就是,Proxy 模式需要的是一个能人,而 Decorator 模式需要的是一个团队。

有些情况下,拥有了一个团队,会更加利于工作分工,而不至于将所有的事情,都让这个能人来干,他终将有一天会 hold 不住的。但有些情况下,人多了反而不好,只需要一个能人就行了。

如果这个比喻不太恰当的话,我就要拿出我的杀手锏了,用代码来说话。

我们先来回忆一下这两段经典的代码,一个接口,一个它的实现类。

public interface Greeting {
 
    void sayHello(String name);
}
public class GreetingImpl implements Greeting {
 
    @Override
    public void sayHello(String name) {
        System.out.println("Hello! " + name);
    }
}

可以使用 Proxy 类来代理 GreetingImpl 类做点事情:
 
public class GreetingProxy implements Greeting {
 
    private GreetingImpl greetingImpl;
 
    public GreetingProxy(GreetingImpl greetingImpl) {
        this.greetingImpl = greetingImpl;
    }
 
    @Override
    public void sayHello(String name) {
        before();
        greetingImpl.sayHello(name);
    }
 
    private void before() {
        System.out.println("Before");
    }
}

只需保证 GreetingProxy 与 GreetingImpl 实现同一个接口 Greeting,并通过构造方法将 GreetingImpl 温柔地射入 GreetingProxy 的身体之中,那么,GreetingProxy 就可以完全拥有 GreetingImpl 了。可以在帮它做正事儿之前,先干点别的事情,比如这里的 before() 方法。想干点什么就干点什么,只要您喜欢,它就喜欢。(此处省略一千字)

以上就是 Proxy 模式,可以认为 GreetingProxy 包装了 GreetingImpl,那么,我们就应该怎样来使用呢?


public class ClientProxy {
 
    public static void main(String[] args) {
        Greeting greeting = new GreetingProxy(new GreetingImpl());
        greeting.sayHello("Jack");
    }
}

很爽吧?下面用一张类图来表达我此时此刻的感觉:

在这里插入图片描述

可见,GreetingProxy 是通过“组合”的方式对 GreetingImpl 进行包装,并对其进行功能扩展。这样,无需修改 GreetingImpl 的任何一行代码,就可以完成它想要做的事情。

说的高深一点,这就是“开闭原则”(可不是一开一闭的意思哦),它是设计模式中一条非常重要的原则,意思就是“对扩展开放,对修改封闭”。没错,我们确实是提供了 GreetingProxy 类来扩展 GreetingImpl 的功能,而并非去修改 GreetingImpl 原有的代码。这就是超牛逼的“开闭原则”了,每个开发人员都需要铭记在心!还需要知道的就是扩展并非只有“继承”这一种方式,这里用到的“组合”也是一种扩展技巧。

其实,以上使用 Proxy 模式实现了 AOP 理论中的 Before Advice(前置增强)功能。如果用户现在来了一个需求,需要在 sayHello 完事之后再记录一点操作日志。那么,我们此时最简单的方法就是给 GreetingProxy 增加一个 after() 方法,代码如下:

public class GreetingProxy implements Greeting {
 
    private GreetingImpl greetingImpl;
 
    public GreetingProxy(GreetingImpl greetingImpl) {
        this.greetingImpl = greetingImpl;
    }
 
    @Override
    public void sayHello(String name) {
        before();
        greetingImpl.sayHello(name);
        after();
    }
 
    private void before() {
        System.out.println("Before");
    }
 
    private void after() {
        System.out.println("After");
    }
}

这样做确实可以实现需求,但您要知道,需求是永无止境的,这个 Proxy 类将来可能会非常庞大,要干的事情会越来越多。一下子是日志记录,一下子是事务控制,还有权限控制,还有数据缓存。把所有的功能都放在这个 Proxy 类中是不明智的,同时这也违反了“开闭原则”。

作为一个牛逼的架构师,有必要来点炫的东西,让那帮程序员小弟们对您投来崇拜的目光。

用 Decorator 模式吧!

先来一张牛图:

搞了一个抽象类 GreetingDecorator 出来,确实挺抽象的,它就是传说中的“装饰器”了,也实现了 Greeting 接口(与 Proxy 模式相同),但却有两点不同:

在装饰器中不是组合实现类 GreetingImpl,而是组合它的接口 Greeting。
下面通过两个 Decorator 的实现类(也就是具体装饰器),来提供多种功能的扩展。
我们不再需要一个能人,而需要一个团队!

如果要加入日志记录功能,可以搞一个日志记录的装饰器;如果要加入事务控制功能,也可以再搞一个事务控制的装饰器;...

想怎么装饰就怎么装饰,这就像您买了一套新房,现在都是毛坯的,您可以刷漆,也可以贴纸,还可以画画,当然可以又刷漆、又贴纸、又画画。

屁话少说,上代码吧!

先来看看这个装饰器:

public abstract class GreetingDecorator implements Greeting {
 
    private Greeting greeting;
 
    public GreetingDecorator(Greeting greeting) {
        this.greeting = greeting;
    }
 
    @Override
    public void sayHello(String name) {
        greeting.sayHello(name);
    }
}


以上是一个很干净的装饰器,没有任何的增强逻辑,只是简单的通过构造方法射入了 Greeting 对象,然后调用它自己的 sayHello() 方法,感觉啥也没干一样。

当然,GreetingDecorator 只是一个抽象的装饰器,要想真正使用它,您得去继承它,实现具体的装饰器才行。

第一个具体装饰器 GreetingBefore:

public class GreetingBefore extends GreetingDecorator {
 
    public GreetingBefore(Greeting greeting) {
        super(greeting);
    }
 
    @Override
    public void sayHello(String name) {
        before();
        super.sayHello(name);
    }
 
    private void before() {
        System.out.println("Before");
    }
}


第二个具体装饰器 GreetingAfter:

public class GreetingBefore extends GreetingDecorator {
 
    public GreetingBefore(Greeting greeting) {
        super(greeting);
    }
 
    @Override
    public void sayHello(String name) {
        before();
        super.sayHello(name);
    }
 
    private void before() {
        System.out.println("Before");
    }
}

需要注意的是,在具体装饰器的构造方法中调用了父类的构造方法,也就是把 Greeting 实例射进去了。在具体装饰器中,完成自己应该完成的事情。真正做到了各施其责,而不是一人包揽。

我们可以这样来用装饰器:

public class ClientDecorator {
 
    public static void main(String[] args) {
        Greeting greeting = new GreetingAfter(new GreetingBefore(new GreetingImpl()));
        greeting.sayHello("Jack");
    }
}

先 new GreetingImpl,再 new GreetingBefore,最后 new GreetingAfter。一层裹一层,就像洋葱一样!但不同的是,裹的顺序是可以交换,比如,先 new GreetingAfter,再 new GreetingBefore。

这种创建对象的方式是不是非常眼熟呢?没错!在 JDK 的 IO 包中也有类似的现象。

比如:想读取一个二进制文件,可以这样获取一个输入流:

InputStream input = new DataInputStream(new BufferedInputStream(new FileInputStream("C:/test.exe")));


其实看看 IO 的类图,就一目了然了,它就用到了 Decorator 模式:

IO 包是一个很强大的包,为了使表达更加简化,以上仅提供了 IO 中的部分类。

到此,想必您已经了解到 Proxy 模式与 Decorator 模式的本质区别了吧?

这里两个模式都是对类的包装,在不改变类自身的情况下,为其添加特定的功能。若这些功能比较单一,可考虑使用 Proxy 模式,但对于功能较多且需动态扩展的情况下,您不妨尝试一下 Decorator 模式吧!


后文:争议的评论

有部分人认为,代理模式和装饰器模式的本质区别是从作用区分的:

装饰者模式是使用的调用者从外部传进来的被装饰对象,调用者只想让你把他给你的对象加强一下,装饰一下. 代理模式使用的是代理对象在自己的构造方法里面new的一个被代理类的对象,不是调用者传入的,调用者不知道你找了其他人,他也不关心这些事,只要你把事情做好就行了.

根据《设计模式之蝉》的描述,代理模式可浅分为强制代理和普通代理,文中应该描述的是强制代理,所以笔者不赞同评论中关于使用的说法。但作用上的区分是有的,代理更多在于控制或完成额外的功能,装饰器更多是对原本功能的加强和修饰。

  • 6
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值