在项目的开发中,经常需要复用一些现成的组件,但是又想在现成组件的基础上拓展一些功能,并且不希望改变组件结构。这就很适合使用装饰器模式来实现。装饰器模式,顾名思义就是对一个对象进行“装饰”。举个生活中的例子,一个白色的蛋糕可以在上面淋上巧克力酱,放上草莓,放上各种小摆件,做成不同样式的生日蛋糕。软件开发也是如此,有时,我们的对象也需要进行“装饰”。这种不改变原有类实现拓展的方式,你可能会想到继承或者动态代理和AOP面向切面编程。很不错,这些方法也能实现以上需求,但是有些情况下,使用装饰器会更加灵活,可以避免继承带来的子类过多的问题或者动态代理和面向切面编程带来复杂度增加的问题。
装饰器模式
定义
装饰器模式(Decorator):在不改变现有对象结构的情况下,动态地赋予对象额外的功能。
装饰器模式的结构
装饰器模式的结构如下:
- 抽象组件(Component):即增加功能时的核心类,它定义了“装饰”所必须的抽象接口。
- 具体组件(ConcreteComponent):具体组件实现了抽象组件中定义的接口。
- 抽象装饰(Decorator):继承抽象组件,并包含具体组件的实例,可以通过其子类扩展具体组件的功能。Decorator类知道自己要装饰的对象。
- 具体装饰(ConcreteDecorator):实现抽象装饰的方法,并给具体组件(ConcreteComponent)添加新功能。
实际应用场景
-
Java中的文件流:在读取文件时,我们经常会使用
new BufferedReader(new FileReader(""));
进行嵌套实例化,这种嵌套调用转型的方式就是一种装饰器模式的体现。如果没有使用这种方式,子类的数目将大幅增长,难以维护。 -
双重认证:在账号密码输入完成之后,往往需要进行手机短信验证码的双重认证。这个时候就可以考虑使用装饰器模式,在原来逻辑不变的情况下可以扩展登录模块。
示例
有些时候,我们点击登录按钮,会跳出移动滑块直至图形拼合的验证码,在通过了人机验证后,又会出现手机短信验证的需求。这个时候,我们就可以采用装饰器模式进行嵌套验证。不用修改登录逻辑。
接下来,我们开始使用装饰器模式实现这个需求。
首先,我们来实现抽象组件。
ILoginComponent.java
package com.yeliheng.decorator;
/**
* 抽象组件
*/
public interface ILoginComponent {
void login();
}
我们在抽象组件中定义了一个登录接口。
接着我们来实现这个接口。
LoginComponentImpl.java
package com.yeliheng.decorator;
/**
* 具体组件
*/
public class LoginComponentImpl implements ILoginComponent{
@Override
public void login() {
System.out.println("登录成功!");
}
}
可以看到,具体组件实现了抽象组件的接口方法。我们为了演示清晰,直接输出登录成功。
这个时候,我们需要为登录接口加上图形验证码和短信验证码。并且在图形验证码通过后才可以使用短信验证码。
装饰器登场了。我们创建一个抽象装饰器。
AbstractDecorator.java
package com.yeliheng.decorator;
/**
* 抽象装饰
*/
public class AbstractDecorator implements ILoginComponent{
protected ILoginComponent component;
public AbstractDecorator(ILoginComponent component) {
this.component = component;
}
@Override
public void login() {
component.login();
}
}
抽象装饰器中,实现了抽象组件。并且包含一个抽象组件实例的引用,以构造函数的方式注入。重写了抽象组件中的login()方法。这样做的目的是将引用建立到后面传入的对象中,以便使用对象中的处理逻辑。
现在我们可以开始写具体的装饰器了。
图像验证码装饰器:
ImageCaptcha.java
package com.yeliheng.decorator;
/**
* 图形验证码-具体装饰
*/
public class ImageCaptcha extends AbstractDecorator{
public ImageCaptcha(ILoginComponent component) {
super(component);
}
@Override
public void login() {
System.out.println("图形验证已通过!");
super.login();
}
}
短信验证码装饰器:
SmsCaptcha.java
package com.yeliheng.decorator;
/**
* 具体装饰-短信验证码
*/
public class SmsCaptcha extends AbstractDecorator{
public SmsCaptcha(ILoginComponent component) {
super(component);
}
@Override
public void login() {
System.out.println("短信验证码验证通过!");
super.login();
}
}
可以看到装饰器继承了抽象装饰器,并且在构造函数中,传入抽象组件的引用,并传给父类,实现嵌套。
最后我们来使用写好的装饰器。
Main.java
package com.yeliheng.decorator;
public class Main {
public static void main(String[] args) {
ImageCaptcha imageCaptcha = new ImageCaptcha(new SmsCaptcha(new LoginComponentImpl()));
imageCaptcha.login();
}
}
在Main函数中,我们嵌套调用,谁要在最前面执行,就把它放到最外层。我们实例化一个图形验证码的装饰器,并且在其中传入短信验证码装饰器,最后将登录组件的具体实现传入到短信验证码的装饰器中。通过一系列巧妙的嵌套调用,我们成功在不修改原有逻辑的情况下引入了两种认证方式便于后期的拓展。
最终代码的执行效果如下:
依次通过了图形验证、短信验证,最后登录成功。
装饰器模式的优缺点
优点
- 装饰器是继承的有力补充,基于继承进行升级,更加灵活。
- 能够在不改变原来对象逻辑的情况下动态进行功能拓展。
- 完全遵循开闭原则。
缺点
- 过度使用装饰器会增加子类,增加程序复杂度。
总结
本文通过对登录接口的嵌套封装改造详细讲解了装饰器模式,完全基于软件开发中的实际场景进行讲解,有助于融会贯通。
示例的完整代码参见:Github