最近在学习Spring源码,尤其是在学习FactoryBean的时候,有涉及到装饰器模式,再结合自己之前学习的设计模式,代理模式觉得和装饰器模式很相似,但是仔细研究后有各有不同,在这篇文章中整理下,希望可以帮助到需要的朋友。
装饰器模式
定义:动态地给一个对象添加一些额外的职责,同时又不改变其结构,就增加功能来说装饰模式比生成子类更为灵活。
Component是定义一个对象接口,可以给这些对象动态的添加职责,ConcreteComponent是定义一个具体的对象,也可以给这个对象动态添加一些职责。Decorator,装饰抽象类,继承Component,从外类来扩展Component类的功能,但对于Component来说,是无需知道Decorator的存在的。至于ConcreteDecorator就是具体的装饰对象,起到给Component添加职责的功能。
主要解决:一般的,我们为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀。
何时使用:在不想增加很多子类的情况下扩展类。
如何解决:将具体功能职责划分,同时继承装饰者模式。
关键代码: 1、Component 类充当抽象角色,不应该具体实现。 2、修饰类引用和继承 Component 类,具体扩展类重写父类方法。
优点:装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
缺点:多层装饰比较复杂。
使用场景: 1、扩展一个类的功能。 2、动态增加功能,动态撤销。
注意事项:可代替继承。
实现代码
//充当抽象角色,不应该具体实现
public interface Component {
void Operation();
}
//有一个具体的对象
public class ConcreteComponent implements Component {
@Override
public void Operation() {
System.out.println("对象具体操作");
}
}
//装饰抽象类继承Component
public abstract class Decorator implements Component {
protected Component component;
public void setComponent(Component component) {
this.component = component;
}
@Override
public void Operation() {
if (component != null) {
component.Operation();
}
}
}
//装饰的具体实现类
public class ConcreteDecoratorA extends Decorator {
@Override
public void Operation() {
super.Operation();
System.out.println("具体装饰对象A的操作");
}
}
public class ConcreteDecoratorB extends Decorator {
@Override
public void Operation() {
super.Operation();
System.out.println("具体装饰对象B的操作");
}
}
public static void main(String[] args) {
ConcreteComponent component = new ConcreteComponent();
ConcreteDecoratorA decoratorA = new ConcreteDecoratorA();
ConcreteDecoratorB decoratorB = new ConcreteDecoratorB();
//装饰模式:首先用ConcreteComponent实例化对象component,
//然后用ConcreteDecoratorA的实例化对象decoratorA包装component
//最后用ConcreteDecoratorB的实例化对象decoratorB包装decoratorA
decoratorA.setComponent(component);
decoratorB.setComponent(decoratorA);
decoratorB.Operation();
}
原理:装饰模式利用 setComponent来对对象进行包装,这样每个装饰对象的实现和如何使用这个对象分离开了。每个装饰对象只关心自己的功能,不需要关心如何被添加到哦对象链中。
代理模式
定义:为其他对象提供一种代理以控制对这个对象的访问。
主要解决:在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。
何时使用:想在访问一个类时做一些控制。
如何解决:增加中间层。
关键代码:实现与被代理类组合。
应用实例: 1、Windows 里面的快捷方式。 2、猪八戒去找高翠兰结果是孙悟空变的,可以这样理解:把高翠兰的外貌抽象出来,高翠兰本人和孙悟空都实现了这个接口,猪八戒访问高翠兰的时候看不出来这个是孙悟空,所以说孙悟空是高翠兰代理类。 3、买火车票不一定在火车站买,也可以去代售点。 4、一张支票或银行存单是账户中资金的代理。支票在市场交易中用来代替现金,并提供对签发人账号上资金的控制。 5、spring aop。
优点: 1、职责清晰。 2、高扩展性。 3、智能化。
缺点: 1、由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。 2、实现代理模式需要额外的工作,有些代理模式的实现非常复杂。
使用场景:按职责来划分,通常有以下使用场景: 1、远程代理。 2、虚拟代理。 3、Copy-on-Write 代理。 4、保护(Protect or Access)代理。 5、Cache代理。 6、防火墙(Firewall)代理。 7、同步化(Synchronization)代理。 8、智能引用(Smart Reference)代理。
实现代码
public interface Subject {
void request();
}
public class RealSubject implements Subject {
@Override
public void request() {
System.out.println("真实的请求");
}
}
public class Proxy implements Subject {
private Subject realSubject;
@Override
public void request() {
if (realSubject == null) {
realSubject = new RealSubject();
}
realSubject.request();
System.out.println("代理请求也被执行");
}
}
public class Demo {
public static void main(String[] args) {
Proxy proxy = new Proxy();
proxy.request();
}
}
装饰器模式和代理模式相同点以及不同点
相同点
两种从设计模式分类来看都属于结构型,因为两者均使用了组合关系。其次两者都能实现对对象方法进行增强处理的效果。
不同点
代理模式,注重对对象某一功能的流程把控和辅助。它可以控制对象做某些事,重心是为了借用对象的功能完成某一流程,而非对象功能如何。
装饰模式,注重对对象功能的扩展,它不关心外界如何调用,只注重对对象功能的加强,装饰后还是对象本身。
举个例子说明两者不同之处,代理和装饰其实从另一个角度更容易去理解两个模式的区别:代理更多的是强调对对象的访问控制,比如说,访问A对象的查询功能时,访问B对象的更新功能时,访问C对象的删除功能时,都需要判断对象是否登陆,那么我需要将判断用户是否登陆的功能抽提出来,并对A对象、B对象和C对象进行代理,使访问它们时都需要去判断用户是否登陆,简单地说就是将某个控制访问权限应用到多个对象上;而装饰器更多的强调给对象加强功能,比如说要给只会唱歌的A对象添加跳舞功能,添加说唱功能等,简单地说就是将多个功能附加在一个对象上。
所以,代理模式注重的是对对象的某一功能的流程把控和辅助,它可以控制对象做某些事,重心是为了借用对象的功能完成某一流程,而非对象功能如何。而装饰模式注重的是对对象功能的扩展,不关心外界如何调用,只注重对对象功能加强,装饰后还是对象本身。
总结
装饰器和代理模式均属于结构型模式,两者都是通过组合原对象的方式,实现对原对象功能额外的处理。两者应用点不同就是装饰器处理完整并不改变对象本身,但是代理模式借助对象功能完成某一流程。简单说装饰器模式为了增强功能,而代理模式是为了加以控制。
题外话,而spring中的FactoryBean的实现更偏向于使用装饰器模式,偏重于对功能进行增强,并未改变对象本身。处理之后从IOC容器中拿到的依旧是自身,而非代理对象。这些内容也会在后续博客 彻底搞懂Factory中继续讲解。
漫漫长途,终有回转;余味苦涩,终有回甘——吾辈需继续加油!