简介

装饰者模式是一种结构型设计模式,它允许你动态地向对象添加新的行为而不影响其原有的行为。它在运行时给对象动态地添加一些额外的职责,通常是在原有的行为基础上,通过装饰器进行一些修饰,实现了更加灵活的代码复用和扩充。

结构

  1. 抽象构件(Component):要求装饰者对象都实现一个抽象接口或抽象类。(接口或抽象类)

  2. 具体构件(ConcreteComponent):定义了一个具体的对象,也就是需要被装饰的对象。

  3. 抽象装饰器(Decorator):持有一个真实的构建对象的引用,并且可以动态给该对象添加新的功能。(接口或抽象类)

  4. 具体装饰器(ConcreteDecorator):实现具体的装饰器,向真实的构建对象添加新功能。

优缺点

优点:

  1. 非常灵活且可扩展,能动态地为对象添加新的职责和行为。

  2. 遵循开闭原则,能够实现代码的可维护性和可扩展性。

  3. 通过使用装饰器对象,可以避免不必要的继承以及子类的数量爆炸性增长问题。

缺点:

  1. 会导致系统变得复杂,增加了许多类和对象的相互关系,需要开发人员具备更高的抽象能力和设计能力。

  2. 增加了代码复杂度,使得项目开发和维护更加困难。

应用场景

生活场景

穿衣服:我们每天都要穿衣服,根据不同的场合和气温选择不同的服装,比如外套、围巾、帽子等,这些都是具体装饰器(Concrete Decorator),而我们就是被装饰的抽象组件(Component)。

手机配件:如手机壳、钢化膜等。都可以看作是装饰者模式的应用,手机就是抽象组件(Component),手机壳、钢化膜等都是具体装饰器(Concrete Decorator)。

电影特效:如烟雾、爆炸、光影等,影片为抽象组件(Component),各种特效就是具体装饰器(Concrete Decorator)。

java场景

IO流的处理:这是一个典型的装饰者模式的应用。InputSteam和OutputStream是最基本的抽象组件(Component),而各种FilterInputSteam和FilterOutputStream就是具体的装饰器,它们可以实现各种不同的IO流处理功能,如缓冲、压缩、加密等等。

数据库连接池:连接池为抽象组件(Component),各种不同的连接池实现(如C3P0、DBCP等)则是具体的装饰器(Concrete Decorator),它们可以实现不同的连接池缓存策略、连接池大小、超时时间等属性。

Spring的AOP:在AOP中,切面就是具体的装饰器(Concrete Decorator),而业务逻辑则是抽象组件(Component),通过动态代理技术,将具体的业务逻辑和切面对象组合起来,我们就可以实现在不修改源码的情况下,动态地为业务逻辑添加新的功能。

示例

下面以英雄联盟中李青(盲僧)学技能为例。其中Hero代表英雄(抽象构件)、BlindMonk(盲僧-具体构件)、SkillDecorator(技能-抽象装饰器)、QWERDecorator为英雄的四个技能(具体装饰器)

Hero(英雄-抽象组件)

/**
 * 英雄
 */
public interface Hero {
 
    /**
     * 学技能
     */
    void learnSkill();
}

BlindMonk(盲僧-具体构件)

import lombok.Data;
/**
 * 盲僧
 */
@Data
public class BlindMonk implements Hero{
 
    private String name;
 
    public BlindMonk(String name){
        this.name=name;
    }
 
    @Override
    public void learnSkill() {
        System.out.println(getName());
    }
}

SkillDecorator(技能装饰-抽象装饰器)

/**
 * 学技能(装饰hero)
 */
public interface SkillDecorator extends Hero{
 
    void learnSkill();
}

QDecorator(Q技能-具体装饰器)

/**
 * Q技能
 */
public class QDecorator implements SkillDecorator{
    private Hero hero;
 
    private String name;
    public QDecorator(Hero hero,String name){
        this.name=name;
        this.hero=hero;
    }
 
    private void learnQ(){
        System.out.println("习得技能"+name);
    }
 
    @Override
    public void learnSkill() {
        hero.learnSkill();
        learnQ();
    }
}

WDecorator(W技能-具体装饰器)

/**
 * W技能
 */
public class WDecorator implements SkillDecorator{
    private Hero hero;
 
    private String name;
    public WDecorator(Hero hero,String name){
        this.name=name;
        this.hero=hero;
    }
 
    private void learnW(){
        System.out.println("习得技能"+name);
    }
 
    @Override
    public void learnSkill() {
        hero.learnSkill();
        learnW();
    }
}

EDecorator(E技能-具体装饰器)

/**
 * E技能
 */
public class EDecorator implements SkillDecorator{
    private Hero hero;
 
    private String name;
    public EDecorator(Hero hero,String name){
        this.name=name;
        this.hero=hero;
    }
 
    private void learnE(){
        System.out.println("习得技能"+name);
    }
 
    @Override
    public void learnSkill() {
        hero.learnSkill();
        learnE();
    }
}

RDecorator(R技能-具体装饰器)

/**
 * R技能
 */
public class RDecorator implements SkillDecorator{
 
    private Hero hero;
 
    private String name;
    public RDecorator(Hero hero,String name){
        this.name=name;
        this.hero=hero;
    }
 
    private void learnR(){
        System.out.println("习得技能"+name);
    }
 
    @Override
    public void learnSkill() {
        hero.learnSkill();
        learnR();
    }
}

测试类

/**
 * 测试类
 */
@SpringBootTest
public class TestDecoration {
 
    @Test
    void testDecoration(){
        QDecorator q=new QDecorator(new BlindMonk("李青"),"Q:天音波/回音击");
        WDecorator w=new WDecorator(q,"W:金钟罩/铁布衫");
        EDecorator e=new EDecorator(w,"E:天雷破/摧筋断骨");
        RDecorator r=new RDecorator(e,"R:猛龙摆尾");
        r.learnSkill();
    }
}

总结

  • 给对象添加一些职责,但是又不想改变其原有的接口和实现。

  • 需要对不同组合的对象进行扩展,从而避免出现由于继承关系带来的类很多问题。

  • 在不使用继承的情况下动态地为一个对象添加一些额外的功能。

  • 需要在程序运行时动态地为对象添加不同的功能,或者为对象添加同时使用多个的功能。

一句话,当遇到需要给某个类添加新功能,但又不能改源代码或不希望影响其他对象的情况下,可以考虑装饰者模式。