装饰者模式

Post on Sep 02, 2021 by Wei Lin

介绍

装饰模式指的是在不必改变原类文件和使用继承的情况下,通过组合的方式动态地给对象增加新功能。

装饰者模式通过将对象包装在一个装饰者类中,该装饰者类具有与原始对象相同的接口,并且可以透明地将客户端的请求传递给原始对象。同时,装饰者类可以在传递请求之前或之后添加自己的行为,从而实现对对象的动态扩展。

装饰者模式的核心思想是通过组合而非继承来实现对对象的扩展。通过将对象包装在装饰者类中,可以在运行时动态地添加、删除或修改对象的行为,而无需修改原始对象的结构。

模式角色

(1) 抽象组件(Component)

​ 抽象组件通常是一个接口或者抽象类,它定义了一些基本的操作,装饰器和具体组件都要实现这个接口或者抽象类。


(2) 具体组件(Concrete Component)

​ 是实现抽象组件接口的一个具体类,它是被装饰的原始对象,装饰器将会给它动态地添加职责。


(3) 抽象装饰器(Decorator)

​ 它包含一个抽象组件的引用,并且定义了与抽象组件相同的接口的类。在抽象装饰器中,可以定义一些与具体装饰器共享的方法和属性。


(4) 具体装饰器(Concrete Decorator)

​ 是实现抽象装饰器接口的一个具体类,它包含了一个抽象组件的引用,并且动态地给这个抽象组件添加职责。具体装饰器通常包含一个构造函数,用来传入一个抽象组件对象,以便给它添加职责。

Demo.png

具体组件就是原类,我们需要通过具体装饰器为它添加新功能。

代码示例

参考:《Head First 设计模式》

实现的功能:定义一个抽象Coffee类,具体的咖啡实现类为DarkRoast,其它配料如Whip、Mocha作为装饰类。

使用代码实现后,一共有6个类:

功能
Coffee 抽象组件(抽象被装饰类),咖啡抽象类
DarkRoast 具体组件(具体被装饰类),具体的咖啡种类
CondimentDecorator 抽象装饰类,由Whip和Mocha继承该类
Whip 具体装饰类,给咖啡加入奶泡
Mocha 具体装饰类,给咖啡加入摩卡
MakeCoffee 测试类

具体代码如下:

package com.Structural;

/**
 * 装饰者(Decorator)模式示例
 */
public class MakeCoffee {
    public static void main(String[] args) {

        // 原始的DarkRoast咖啡
        Coffee coffee1 = new DarkRoast();
        System.out.println(coffee1.getDescription()
                + " $" + coffee1.cost());

        // 加两份Mocha和一份Whip
        Coffee coffee2 = new DarkRoast();
        coffee2 = new Mocha(coffee2);
        coffee2 = new Mocha(coffee2);
        coffee2 = new Whip(coffee2);
        System.out.println(coffee2.getDescription()
                + " $" + coffee2.cost());

        // 加一份Mocha和一份Whip
        Coffee coffee3 = new DarkRoast();
        coffee3 = new Mocha(coffee3);
        coffee3 = new Whip(coffee3);
        System.out.println(coffee3.getDescription()
                + " $" + coffee3.cost());
    }
}

/**
 * 抽象组件(抽象被装饰类)——基本的咖啡类
 * 作用:定义接口,装饰器和具体组件都要实现这个接口或者抽象类。
 */
abstract class Coffee {
    String description = "Unknown Beverage";

    public String getDescription() {
        return description;
    }

    public abstract double cost();
}

/**
 * 具体组件(具体被装饰类)——DarkRoast(焦炒)表示一种具体的咖啡,也是被装饰者
 * 它继承抽象的被装饰者类,并实现其中的抽象方法。
 */
class DarkRoast extends Coffee {
    public DarkRoast() {
        description = "Dark Roast Coffee";
    }

    public double cost() {
        return 0.99;
    }
}

/**
 * 抽象装饰者类——CondimentDecorator(调味品装饰类)
 * 1.抽象装饰者类中继承该抽象类以保持接口规范
 * 2.包含该抽象类的引用以通过多态的方式对多种被装饰者类进行装饰。
 */
abstract class CondimentDecorator extends Coffee {
    Coffee coffee;
}

/**
 * 装饰者类——奶泡
 * 给具体被装饰类DarkRoast加入奶泡
 */
class Whip extends CondimentDecorator {
    public Whip(Coffee coffee) {
        this.coffee = coffee;
    }

    public String getDescription() {
        return coffee.getDescription() + ", Whip";
    }

    public double cost() {
        return 0.10 + coffee.cost();
    }
}

/**
 * 装饰者类——摩卡
 * 给具体被装饰类DarkRoast加入摩卡
 */
class Mocha extends CondimentDecorator {
    public Mocha(Coffee coffee) {
        this.coffee = coffee;
    }

    public String getDescription() {
        return coffee.getDescription() + ", Mocha";
    }

    public double cost() {
        return 0.20 + coffee.cost();
    }
}

输出为:

Dark Roast Coffee $0.99
Dark Roast Coffee, Mocha, Mocha, Whip $1.49
Dark Roast Coffee, Mocha, Whip $1.29

应用场景

​ 装饰者模式使用一种类似于递归的方式,不断地包装原始对象,从而实现对对象的动态扩展。下面是几个常见的装饰者模式使用场景。


(1) GUI 界面中的装饰器

​ 在 GUI 界面中,可以使用装饰器模式来动态地添加界面组件。例如,可以使用一个基本的文本框组件作为原始对象,然后通过装饰器来添加边框、滚动条、背景等效果。


(2) Java I/O 流中的装饰器

​ Java I/O 流中的输入流和输出流都是以装饰者模式实现的。例如,可以使用一个基本的文件输入流作为原始对象,然后通过装饰器来添加缓冲、转换、加密等功能。


(3) 订单系统中的装饰器

​ 在订单系统中,可以使用装饰者模式来实现不同的订单处理方式。例如,可以使用一个基本的订单处理对象作为原始对象,然后通过装饰器来添加支付、发货、退款等功能。


(4) 缓存系统中的装饰器

​ 在缓存系统中,可以使用装饰者模式来实现不同的缓存策略。例如,可以使用一个基本的缓存对象作为原始对象,然后通过装饰器来添加 LRU 策略、过期策略、分布式策略等。


​ 总之,装饰者模式适用于需要动态地扩展对象功能的场景,它可以避免使用继承来扩展对象功能所带来的类爆炸问题。同时,装饰者模式也使得对象功能的扩展更加灵活、可配置和可组合。

Android中的应用-Context

角色
Context 抽象组件(抽象被装饰者),它给出了抽象接口
ContextImpl 具体组件(具体被装饰者),定义一个具体实现的类
ContexWrapper 抽象装饰类,持有一个ContextImpl对象的实例,并实现一个与抽象构件接口一致的接口
Activity,Service等 具体装饰类,负责给ContextImpl添加上额外的功能

以Activity为例,看装饰器模式在Context中的应用。

​ Activity创建过程中,会执行到AT.performLaunchActivity(),会在这里创建当前Activity的Context实例(ContextImpl对象),该Context实例最终通过createActivityContext()创建并返回,调用栈如下:

ActivityThread.performLaunchActivity() ->
ActivityThread.createBaseContextForActivity() ->
ContextImpl.createActivityContext()

创建具体组件ContextImpl之后,要将其添加到装饰器ContexWrapper中,调用栈如下:

ActivityThread.performLaunchActivity() ->
Activity.attach() ->
Activity.attachBaseContext ->
ContextThemeWrapper.attachBaseContext() ->
ContextWrapper.attachBaseContext()

总的流程为:

  • 创建具体被装饰类ContextImpl;
  • 创建具体装饰类Activity,继承链为Activity->ContextThemeWrapper->ContextWrapper;
  • 将Activity和ContextImpl关联起来,即Activity持有ContextImpl的引用。