装饰器模式

105 阅读8分钟

1. 案例

1.1 案例一

现在有一杯白开水(WaterClass),想向白开水里面加茶叶(TeaClass),加点糖(SugarClass),或者加点盐(SaltClass)。也许会认为那还不简单吗,加茶叶的白开水就再生成一个(WaterWithTeaClass),加糖的白开水就再生成一个(WaterWithSugarClass)这样不就行了吗?但是如果我们既要加糖又要加茶叶,或者加盐加糖这样的组合会很多,可能就要生成很多的类,造成类泛滥。这时候就要用到装饰者模式了

图片

Component

Component 用于表示装饰者和被装饰者共同的父类,是一个接口或者抽象类,定义了属性或者方法,方法的实现可以由子类实现或者自己实现。通常不会直接使用该类,而是通过继承该类来实现特定的功能,它约束了整个继承树的行为。比如说,如果Component代表人,即使通过装饰也不会使人变成别的动物。

此处定义 DrinkComponent 接口用于定义喝水操作

public interface DrinkComponent {

    public abstract void operation();
}
WaterComponent:(被装饰者)

ConcreteComponent:ConcreteComponent是Component的子类,实现了相应的方法,它充当了“被装饰者”的角色。

此处的WaterComponent是一个ConcreteComponent。

public class WaterComponent implements DrinkComponent{

    @Override
    public void operation() {
        // TODO Auto-generated method stub
        System.out.print("water drink");
    }

}
Decorator

Decorator:也是Component的子类,它是装饰者共同实现的抽象类(也可以是接口)。比如说,Decorator代表衣服这一类装饰者,那么它的子类应该是T恤、裙子这样的具体的装饰者。

可以认为是装饰者的父类,然后Decorator实现Component

public class DrinkDecorator implements DrinkComponent {

    DrinkComponent drinkComponent;

    public DrinkDecorator(DrinkComponent drinkComponent){
        super();
        this.drinkComponent = drinkComponent;
    }
    @Override
    public void operation() {

    }
}
装饰者实现类
  • SugarDecorator:(装饰者)
public class SugarDecorator extends DrinkDecorator{

    public SugarDecorator(DrinkComponent component) {
        super(component);
        // TODO Auto-generated constructor stub
    }
    public void operation()
    {
        component.operation();
        System.out.print(",with sugar");
    }
}
  • TeaDecorator:(装饰者)
public class TeaDecorator extends DrinkDecorator{

    public TeaDecorator(DrinkComponent component) {
        super(component);
        // TODO Auto-generated constructor stub
    }
    public void operation()
    {
        component.operation();
        System.out.print(",with Tea");
    }
}
操作函数

MainClass:

public class MainClass {
    public static void main(String[] args) {
        WaterComponent water = new WaterComponent();
        // 往白开水里面加糖
        SugarDecorator sugar = new SugarDecorator(water);
        // 往加糖的白开水里面加茶叶
        TeaDecorator tea = new TeaDecorator(sugar);
        tea.operation();

    }
}

1.2 装饰模式的角色

装饰者角色结构如下图所示: 

图片

  • Component:装饰者和被装饰者共同的父类,是一个接口或者抽象类,定义了属性或者方法,方法的实现可以由子类实现或者自己实现。通常不会直接使用该类,而是通过继承该类来实现特定的功能,它约束了整个继承树的行为。比如说,如果Component代表人,即使通过装饰也不会使人变成别的动物。

  • ConcreteComponent:ConcreteComponent是Component的子类,实现了相应的方法,它充当了“被装饰者”的角色。

  • Decorator:也是Component的子类,它是装饰者共同实现的抽象类(也可以是接口)。比如说,Decorator代表衣服这一类装饰者,那么它的子类应该是T恤、裙子这样的具体的装饰者。

  • ConcreteDecorator:是Decorator的子类,是具体的装饰者,由于它同时也是Component的子类,因此它能方便地拓展Component的状态(比如添加新的方法)。用于扩展ConcreteComponent。

装饰器类(Decorator的子类)和原始类(ConcreteComponent)继承同样的父类,这样我们可以对原始类“嵌套”多个装饰器类。比如,下面这样一段代码,我们对 FileInputStream 嵌套了两个装饰器类:BufferedInputStream 和 DataInputStream,让它既支持缓存读取,又支持按照基本数据类型来读取数据。

InputStream in = new FileInputStream("/user/wangzheng/test.txt");
InputStream bin = new BufferedInputStream(in);
DataInputStream din = new DataInputStream(bin);
int data = din.readInt();

image.png

装饰器类是对功能的增强,这也是装饰器模式应用场景的一个重要特点。实际上,符合“组合关系”这种代码结构的设计模式有很多,比如代理模式、桥接模式,还有现在的装饰器模式。尽管它们的代码结构很相似,但是每种设计模式的意图是不同的。比如拿代理模式和装饰器模式来说代理模式中,代理类附加的是跟原始类无关的功能,而在装饰器模式中,装饰器类附加的是跟原始类相关的增强功能。

// 代理模式的代码结构(下面的接口也可以替换成抽象类)
public interface IA {
  void f();
}
public class A impelements IA {
  public void f() { //... }
}
public class AProxy impements IA {
  private IA a;
  public AProxy(IA a) {
    this.a = a;
  }
  
  public void f() {
    // 新添加的代理逻辑
    a.f();
    // 新添加的代理逻辑
  }
}
// 装饰器模式的代码结构(下面的接口也可以替换成抽象类)
public interface IA {
  void f();
}
public class A impelements IA {
  public void f() { //... }
}
public class ADecorator impements IA {
  private IA a;
  public ADecorator(IA a) {
    this.a = a;
  }
  
  public void f() {
    // 功能增强代码
    a.f();
    // 功能增强代码
  }
}

1.3 另一个Demo

基于装饰者模式实现咖啡厅购买咖啡的操作, 用户在下订单的时候可以为咖啡添加牛奶 巧克力等。并计算出添加配料后的价格。

package com.evan.decorator;

import lombok.Data;

/**
 * @Description
 * @ClassName Drink
 * @Author Evan
 * @date 2019.12.02 22:58
 */
@Data
public abstract class Drink {

    // 描述
    public String des;
    // 价格
    private float price = 0.0f;
    
    // 总价
    public abstract float cost();
}
package com.evan.decorator;

/**
 * @Description
 * @ClassName Caffee
 * @Author Evan
 * @date 2019.12.02 22:58
 */
public class Coffee extends Drink {


    @Override
    public float cost() {
        return super.getPrice();
    }
}
package com.evan.decorator;

/**
 * @Description
 * @ClassName BlackCoffee
 * @Author Evan
 * @date 2019.12.02 23:00
 */
public class BlackCoffee extends Coffee {
    public BlackCoffee() {
        setDes(" BlackCoffee ");
        setPrice(4.0f);
    }
}
package com.evan.decorator;

/**
 * @Description 美式咖啡
 * @ClassName LongBackCoffee
 * @Author Evan
 * @date 2019.12.02 22:58
 */
public class LongBackCoffee extends Coffee {
    public LongBackCoffee() {
        setDes(" LongBackCoffee ");
        setPrice(5.0f);
    }
}
package com.evan.decorator;

/**
 * @Description
 * @ClassName LatteCoffee 拿铁
 * @Author Evan
 * @date 2019.12.02 23:07
 */
public class LatteCoffee extends Coffee {
    public LatteCoffee() {
        setDes(" LatteCoffee ");
        setPrice(6.0f);
    }
}
package com.evan.decorator;

/**
 * @Description
 * @ClassName Decorator 装饰者的父类
 * @Author Evan
 * @date 2019.12.02 23:09
 */
public class Decorator extends Drink {
    // 被装饰者
    private Drink obj;

    public Decorator(Drink obj) { // 组合
        this.obj = obj;
    }

    @Override
    public float cost() {
        // getPrice 自己的价格
        return super.getPrice() + obj.cost();
    }

    @Override
    public String getDes() {
        // obj.getDes()
        return des + " " + getPrice() + " && " + obj.getDes();
    }


}
package com.evan.decorator;

/**
 * @Description
 * @ClassName Milk 牛奶
 * @Author Evan
 * @date 2019.12.02 23:17
 */
public class Milk extends Decorator {
    public Milk(Drink obj) {
        super(obj);
        setDes("牛奶 ");
        setPrice(3.0f); // 牛奶的价格
    }
}
package com.evan.decorator;

/**
 * @Description
 * @ClassName Chocolate 巧克力
 * @Author Evan
 * @date 2019.12.02 23:15
 */
public class Chocolate extends Decorator {
    public Chocolate(Drink obj) {
        super(obj);
        setDes("巧克力 ");
        setPrice(3.0f); // 巧克力的价格
    }
}
package com.evan.decorator;

/**
 * @Description
 * @ClassName Soy  豆浆
 * @Author Evan
 * @date 2019.12.02 23:18
 */
public class Soy extends Decorator{

    public Soy(Drink obj) {
        super(obj);
        setDes("豆浆");
        setPrice(1.5f);
    }

}

点单入口

package com.evan.decorator;

/**
 * @Description
 * @ClassName CoffeeBar 
 * @Author Evan
 * @date 2019.12.02 23:19
 */
public class CoffeeBar {
    public static void main(String[] args) {

        // 装饰者模式下的订单:2份巧克力+一份牛奶的LongBlack

        // 1. 点一份 LongBlack
        Drink order = new LongBackCoffee();
        System.out.println("费用1=" + order.cost());
        System.out.println("描述=" + order.getDes());

        // 2. order 加入一份牛奶
        order = new Milk(order);

        System.out.println("order 加入一份牛奶 费用 =" + order.cost());
        System.out.println("order 加入一份牛奶 描述 = " + order.getDes());

        // 3. order 加入一份巧克力

        order = new Chocolate(order);

        System.out.println("order 加入一份牛奶 加入一份巧克力  费用 =" + order.cost());
        System.out.println("order 加入一份牛奶 加入一份巧克力 描述 = " + order.getDes());

        // 3. order 加入一份巧克力

        order = new Chocolate(order);

        System.out.println("order 加入一份牛奶 加入2份巧克力   费用 =" + order.cost());
        System.out.println("order 加入一份牛奶 加入2份巧克力 描述 = " + order.getDes());

        System.out.println("===========================");

        Drink order2 = new BlackCoffee();

        System.out.println("order2 黑咖啡  费用 =" + order2.cost());
        System.out.println("order2 黑咖啡 描述 = " + order2.getDes());

        order2 = new Milk(order2);

        System.out.println("order2 黑咖啡 加入一份牛奶  费用 =" + order2.cost());
        System.out.println("order2 黑咖啡 加入一份牛奶 描述 = " + order2.getDes());


    }

}
费用1=5.0
描述= LongBackCoffee
order 加入一份牛奶 费用 =8.0
order 加入一份牛奶 描述 = 牛奶  3.0 &&  LongBackCoffee
order 加入一份牛奶 加入一份巧克力  费用 =11.0
order 加入一份牛奶 加入一份巧克力 描述 = 巧克力  3.0 && 牛奶  3.0 &&  LongBackCoffee
order 加入一份牛奶 加入2份巧克力   费用 =14.0
order 加入一份牛奶 加入2份巧克力 描述 = 巧克力  3.0 && 巧克力  3.0 && 牛奶  3.0 &&  LongBackCoffee
===========================
order2 黑咖啡  费用 =4.0
order2 黑咖啡 描述 =  BlackCoffee
order2 黑咖啡 加入一份牛奶  费用 =7.0
order2 黑咖啡 加入一份牛奶 描述 = 牛奶  3.0 &&  BlackCoffee

2 装饰器模式总结

装饰者模式:动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。在如下三种情况下可以选择使用装饰者模式。

  1. 需要扩展一个类的功能,或给一个类增加附加责任。
  2. 需要动态的给一个对象增加功能,这些功能可以再动态地撤销。
  3. 需要增加一些基本功能的排列组合而产生的非常大量的功能,从而使继承变得不现实。

意图:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。

主要解决:一般的,我们为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀。

装饰器模式主要解决继承关系过于复杂的问题,通过组合来替代继承。它主要的作用是给原始类添加增强功能。这也是判断是否该用装饰器模式的一个重要的依据。除此之外,装饰器模式还有一个特点,那就是可以对原始类嵌套使用多个装饰器。为了满足这个应用场景,在设计的时候,装饰器类需要跟原始类继承相同的抽象类或者接口。

  • 装饰模式又名包装(Wrapper)模式。
  • 装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案。
  • 装饰模式以对客户端透明的方式动态地给一个对象附加上更多的责任。换言之,客户端并不会觉得对象在装饰前和装饰后有什么不同。
  • 装饰模式可以在不创造更多子类的情况下,将对象的功能加以扩展。
  • 装饰模式把客户端的调用委派到被装饰类。装饰模式的关键在于这种扩展是完全透明的。