一、概述

装饰器模式是一种结构型设计模式,可以做到在不改变原来对象功能的情况下,向原有的对象添加新的功能,起到一个装饰的作用。具体的做法是创建一个装饰器类,用来包装原有的类,在不改变原有类方法的情况下,为原有类添加新的功能。

  • 1、什么时候使用
  1. 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责;
  2. 处理那些可以撤销的职责;
  3. 当不能采用生成子类的方式进行扩充时。一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。另一种情况可能是因为类定义被隐藏,或类定义不能用于生成子类。
  • 2、四大角色

1)抽象装饰对象角色(Component):接口,被装饰对象的基类,提供被装饰得方法,被装饰对象和装饰器都实现该接口;
2)具体装饰对象角色(ConcreteComponent):具体被装饰对象,它继承自Component,并实现需要被装饰的方法基本内容;
3)抽象装饰器角色(Decorator):抽象类,维持一个装饰对象实例的引用,所有具体装饰器对象的父类,完成装饰器的部分职能。可以只对被装饰的对象进行一些简单的包裹,也可包含对被装饰的对象中方法的实现;
4)具体装饰器角色(ConcreteDecorator):完成具体的装饰功能。装饰功能的实现是通过调用被装饰对象对应的方法,加上装饰对象自身的方法,这是装饰器模式动机中的添加额外功能的关键。可以有多个具体装饰角色,但是要注意各装饰之间的调用顺序。

二、代码示例

来看一个例子,我们在外面吃饭,有很多食物,其中有烧烤和火锅。比如我们点了烧烤,但是觉得味道不够爽,所以我们选择让老板加盐,或者加辣椒,这里的加盐和加辣椒其实就是对事物起装饰作用。用代码实现如下:

1、抽象装饰对象角色(Component):创建Food接口

/**
 * Created by yd on 2019/4/2.
 * 抽象装饰对象角色(Component):接口,被装饰对象的基类,提供被装饰得方法,被装饰对象和装饰器都实现该接口
 */
public interface Food {
    String getDesc();

    String getCost();
}

2、具体装饰对象角色(ConcreteComponent):创建实现接口的实现类

Barbecue实现类

/**
 * Created by yd on 2019/4/2.
 * 具体装饰对象角色(ConcreteComponent):具体被装饰对象,它继承自Component,并实现需要被装饰的方法基本内容;
 */
public class BarbecueFood implements Food {

    @Override
    public String getDesc() {
        return "烧烤";
    }

    @Override
    public String getCost() {
        Double cost = 3.0;
        return String.format("最后价格:%s块", cost);
    }
}

Hotpot实现类

/**
 * Created by qxr4383 on 2019/4/2.
 * 抽象装饰对象角色(Component):接口,被装饰对象的基类,提供被装饰得方法,被装饰对象和装饰器都实现该接口
 */
public class HotpotFood implements Food {
    @Override
    public String getDesc() {
        return "火锅";
    }

    @Override
    public String getCost() {
        Double cost = 100.0;
        return String.format("最后价格:%s块", cost);
    }
}

3、抽象装饰器角色(Decorator):创建装饰器基类
FoodDecorator是一个抽象类,其中组合Food类

/**
 * Created by qxr4383 on 2019/4/2.
 * FoodDecorator是一个抽象类,其中组合Food类
 */
public abstract class FoodDecorator implements Food{
    protected Food food;

    public FoodDecorator(Food food) {
        this.food = food;
    }

    public String getDesc(){
        return food.getDesc();
    }

    public String getCost(){
        return food.getCost();
    }
}

4、具体装饰器角色(ConcreteDecorator)
创建SaltFoodDecorator装饰器类

/**
 * Created by yd on 2019/4/2.
 *创建SaltFoodDecorator装饰器类
 */
public class SaltFoodDecorator extends FoodDecorator {

    public SaltFoodDecorator(Food food){
        super(food);
    }
    @Override
    public String getDesc() {
        String result = "加盐的" + food.getDesc();

        return result;
    }
    public String getCost() {
        Double salt = 2.0;
        System.out.println(String.format("加盐多收%s块", salt));
        String result = food.getCost() + " + " + salt + "块";

        return result;
    }
}

创建PepperFoodDecorator装饰器类

/**
 * Created by yd on 2019/4/2.
 * 创建PepperFoodDecorator装饰器类
 */
public class PepperFoodDecorator extends FoodDecorator {
    public PepperFoodDecorator(Food food){
        super(food);
    }

    @Override
    public String getDesc() {
        String result = "加盐的" + food.getDesc();

        return result;
    }

    @Override
    public String getCost() {
        Double pepper = 10.0;
        System.out.println(String.format("加辣椒多收%s块", pepper));
        String result = food.getCost() + " + " + pepper + "块";
        return result;
    }
}

最后来测试一下

/**
 * Created by qxr4383 on 2019/4/2.
 */
public class Test {
    public static void main(String[] args) {
        //创建不用装饰器修饰的Food
        Food food = new BarbecueFood();
        display(food.getDesc());
        display(food.getCost());
        System.out.println("-------------分割线---------------");
        //创建用SaltFoodDecorator装饰的Food
        Food barbecue = new SaltFoodDecorator(new BarbecueFood());
        display(barbecue.getDesc());
        display(barbecue.getCost());
        System.out.println("-------------分割线---------------");
        //创建用PepperFoodDecorator装饰的Food
        Food hotSpot = new PepperFoodDecorator(new HotpotFood());
        display(hotSpot.getDesc());
        display(hotSpot.getCost());
    }

    private static void display(Object obj) {
        System.out.println(obj);
    }

}

执行结果:

烧烤
最后价格:3.0块
-------------分割线---------------
加盐的烧烤
加盐多收2.0块
最后价格:3.0块 + 2.0块
-------------分割线---------------
加盐的火锅
加辣椒多收10.0块
最后价格:100.0块 + 10.0块

来看一下类图会更加地清晰

JAVA设计模式之装饰器模式_System

END

装饰器模式在日常开发中也有很多应用,典型的就是JDK里面的IO。

JAVA设计模式之装饰器模式_System_02

InputStream代表输入流,输入来源可以是文件(FileInputStream)、管道(PipedInputStream)、数组(ByteArrayInputStream)等。就像上面的烧烤,火锅。FilterInputStream就是装饰器的基类,他的实现类是一系列的装饰器,比如BufferedInputStream可以用缓冲区来修饰InputStream,把InputSteam包装成有缓冲区的输入流。