一天一种JAVA设计模式之七:装饰模式

295 阅读5分钟

写在前面的话

复习、总结23种设计模式

获取详细源码请点击我

上一篇

# 一天一种JAVA设计模式之六:适配器模式

装饰模式

记重点

如果你不想在修改原有类的基础上增强新的功能,那就使用装饰模式吧

定义

简单来说,就是用一系列包装类来增强原始类的功能,而不是直接修改原始类。

它是一种结构型设计模式。在该模式中,我们使用了一个装饰器类,该类将原始类包装在其内部,并提供与原始类相同的接口。由于该类遵循同样的接口,因此无论是使用原始类还是装饰器类,都可以正常工作。

装饰器模式通常用于以下两种情况:

  1. 当你需要透明地添加功能时,而不希望增加对象子类来实现所需的功能时,可以使用该模式。
  2. 当需要动态地为对象添加功能时,可以使用装饰器模式。

装饰器模式分为四个部分:接口、具体组件、装饰器、具体装饰器。其中,接口定义了组件和装饰器的公共行为,具体组件实现了组件接口,并承担了被装饰的原始类,装饰器实现了组件接口并包装了具体组件,具体装饰器继承了装饰器并添加了一些额外的行为。

装饰模式的使用场景

装饰器模式在以下三种情况下非常适用。

  1. 需要动态地添加对象功能

    在不影响其他对象的情况下添加新功能。你可以使用装饰器来改变对象的行为,而不必修改底层对象。这种方式比继承更加灵活,因为它允许你在运行时添加或删除功能。

  2. 需要透明地添加对象功能

    装饰器模式允许你以透明的方式为对象添加功能。这意味着你可以将多个装饰器一起使用,而不必担心它们如何相互作用。因为装饰器遵循相同的接口,因此你可以在不了解其内部实现的情况下使用它们。

  3. 需要保持类简单、干净

    如果你的类有太多的责任,那么你可以使用装饰器模式来分离这些责任。这将使你的代码更加容易维护和测试。装饰器允许你将一些特定的责任委托给单独的类,以便你可以更好地关注原始类的核心功能。

装饰模式的通用类图

image.png

在类图中有四个角色需要说明

Component抽象构建

Component是一个接口或抽象类,就是定义我们最核心的对象,也就是最原始的对象

//抽象构件角色  
public interface Component {  
    void operation();  
}

ConcreteComponent具体构建

ConcreteComponent是最核心、最原始、最基本的接口或抽象类的实现,我们要装饰的就是它

//具体构件角色  
public class ConcreteComponent implements Component {  
  
    public ConcreteComponent() {  
        System.out.println("创建具体构件角色");  
    }  

    @Override  
    public void operation() {  
        System.out.println("调用具体构件角色的方法operation()");  
    }  
}

Decorator装饰角色

一般是一个抽象类,用什么用呢?实现接口或抽象方法,它里面不一定有抽象的方法,但在它的属性中必然有一个private变量指向Component抽象构建

//抽象装饰角色  
public class Decorator implements Component {  
  
    private Component component;  

    public Decorator(Component component) {  
        this.component = component;  
    }  

    @Override  
    public void operation() {  
        component.operation();  
    }  
}

ConcreteDecorator具体装饰角色

//具体装饰角色  
public class ConcreteDecorator extends Decorator {  
  
    public ConcreteDecorator(Component component) {  
        super(component);  
    }  

    @Override  
    public void operation() {  
        super.operation();  
        addedFunction();  
    }  

    public void addedFunction() {  
        System.out.println("为具体构件角色增加额外的功能addedFunction()");  
    }  
  
}

DEMO-1

需要被装饰的目标接口

package com.design.pattern.decorator.test02;  
  
public interface Person {  

    void run();  
}  

// 一个很普通的人,没有经过装饰过的
class Man implements Person {  
    @Override  
    public void run() {  
        System.out.println("我终于跑起来了");  
    }  
}

装饰器

package com.design.pattern.decorator.test02;  
  
public abstract class PersonDecorator implements Person {  
  
    private Person person;  

    public PersonDecorator(Person person) {  
        this.person = person;  
    }  

    @Override  
    public void run() {  
        person.run();  
    }  
}  

// 使用飞机来装饰这个人
class PersonWithPlaneDecorator extends PersonDecorator {  
  
    public PersonWithPlaneDecorator(Person person) {  
        super(person);  
    }  

    @Override  
    public void run() {  
        System.out.println("在我成为飞行员后");  
        super.run();  
    }  
}

测试类


package com.design.pattern.decorator.test02;  
  
public class Client {  
  
    public static void main(String[] args) {  
        // 我们要装饰的目标对象  
        Person man = new Man();  

        // 1次装饰  
        PersonDecorator decorator1 = new PersonWithPlaneDecorator(man);  
        // 2次装饰  
        decorator1 = new PersonWithPlaneDecorator(decorator1);  

        decorator1.run();  
    }  
}

最佳实践

装饰模式是对继承的有力补充。你要知道继承不是万能的,继承可以解决实际的问题,但是在项目中你要考虑诸如易维护、易扩展、易复用等,而且在一些情况下(比如上面那个成绩单例子)你要是用继承就会增加很多子类,而且灵活性非常差,那当然维护也不容易了,也就是说装饰模式可以替代继承,解决我们类膨胀的问题。同时,你还要知道继承是静态地给类增加功能,而装饰模式则是动态地增加功能,在上面的那个例子中,我不想要SortDecorator这层的封装也很简单,于是直接在Father中去掉就可以了,如果你用继承就必须修改程序。

装饰模式还有一个非常好的优点:扩展性非常好。在一个项目中,你会有非常多的因素考虑不到,特别是业务的变更,不时地冒出一个需求,尤其是提出一个令项目大量延迟的需求时,那种心情是相当的难受!装饰模式可以给我们很好的帮助,通过装饰模式重新封装一个类,而不是通过继承来完成,简单点说,三个继承关系Father、Son、GrandSon三个类,我要在Son类上增强一些功能怎么办?我想你会坚决地顶回去!不允许,对了,为什么呢?你增强的功能是修改Son类中的方法吗?增加方法吗?对GrandSon的影响呢?特别是GrandSon有多个的情况,你会怎么办?这个评估的工作量就够你受的,所以这是不允许的,那还是要解决问题的呀,怎么办?通过建立SonDecorator类来修饰Son,相当于创建了一个新的类,这个对原有程序没有变更,通过扩展很好地完成了这次变更。

下一篇

# 一天一种JAVA设计模式之六:适配器模式