结合案例深刻解析装饰者模式

1、基本概念

装饰者模式是结构型设计模式。java

装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案。git

容许向一个现有的对象添加新的功能。同时又不改变其结构,它是做为现有的类的一个包装。github

主要解决的问题: 通常咱们为了扩展一个类常常使用继承方式实现,因为继承为类引入静态特征,而且随着扩展功能的增多,子类会很膨胀设计模式

2、结构

结构:数组

  • 装饰者(Decorator)和具体组件(ConcreteComponent)都继承自组件(Component);
  • 所谓装饰,就是把这个装饰者套在被装饰者之上,从而动态扩展被装饰者的功能;
  • 装饰者的方法有一部分是本身的,这属于它的功能(半透明的装饰者模式)。而后调用被装饰者的方法实现,从而也保留了被装饰者的功能;

03_decorator_02.png

3、案例

一、装饰者模式案例

模拟在餐馆点饮料,咱们能够点咖啡,而咖啡有Decaf咖啡和Espresso咖啡,而这两种咖啡均可以加牛奶和巧克力进去。bash

具体的代码组织结构图:ide

03_decorator_01.png

具体代码:函数

先看最高的component包下的Drink类:性能

/**
 * Component的超类
 * 单品和装饰者都要继承自这个类
 */
public abstract class Drink {

    private String description = ""; //一开始没有描述
    private double price = 0; //一开始价格为0

    /**
     * 抽象方法
     *  一、若是是单品的话就直接是本身的价格
     *  二、若是是装饰者的话就还要加上装饰品本身的价格
     */
    public abstract double cost();


    // setter getter

    public String getDescription() {
        return description;
    }
    public double getPrice() {
        return price;
    }
    public void setDescription(String description) { //描述的时候顺便把价格描述一下
        this.description = description;
    }
    public void setPrice(double price) {
        this.price = price;
    }
}复制代码

下面看两个具体的Component:测试

/** ConcreteComponent 1*/
public class Decaf extends Drink {

    public Decaf() {
        super.setDescription("Decaf");
        super.setPrice(3); //3块钱
    }

    @Override
    public double cost() {
        return getPrice();//super.getPrice()//这个就是父类的价格(本身什么也没加 (没有被装饰))
    }

    // 重写getter 后面加上本身的花费
    @Override
    public String getDescription() {
        return super.getDescription() + "-" + cost();
    }
}
复制代码
/** ConcreteComponent 2
 *  也能够在ConcreteComponent和Drink类有一个过渡的类)  (好比Coffee类)
 */
public class Espresso extends Drink {

    public Espresso(){
        super.setDescription("Espresso");
        super.setPrice(4);
    }

    @Override
    public double cost() {
        return getPrice();//super.getPrice()//这个就是父类的价格(本身什么也没加)
    }

    @Override
    public String getDescription() {
        return super.getDescription() + "-" + cost();
    }
}复制代码

下面看decorator下的三个类:

第一个是装饰者的超类,继承自Drink类:

public class Decorator extends Drink{
    /**
     * 这个引用很重要,能够是单品,也能够是被包装过的类型,因此使用的是超类的对象
     * 这个就是要被包装的单品(被装饰的对象)
     */
    private Drink drink; //这里要拿到父类的引用,由于要控制另外一个分支(具体的组件)

    public Decorator(Drink drink) {
        this.drink = drink;
    }

    /**
     * 若是drink是已经被装包过的,那么就会产生递归调用  最终到单品
     */
    @Override
    public double cost() {
        return super.getPrice() + drink.cost(); // 本身的价格和被包装单品的价格
    }

    @Override
    public String getDescription() {
        return super.getDescription() + "-" + super.getPrice()
                + " && " + drink.getDescription();
    }
}复制代码

而后是两个装饰者:

/**
 * 这个是具体的装饰者() --> 继承自中间的装饰着Decorator
 */
public class Chocolate extends Decorator{

    public Chocolate(Drink drink) { //若是父类搞了一个 带参数的构造函数,子类必须显示的使用super调用
        super(drink);
        super.setDescription("Chocolate");
        super.setPrice(1);
    }
}复制代码
public class Milk extends Decorator{

    public Milk(Drink drink) {
        super(drink); //调用父类的构造函数
        super.setDescription("Milk");
        super.setPrice(3);
    }
}复制代码

测试类:

public class MyTest {
    public static void main(String[] args) {
        //只点一个单品 (Decaf 咖啡)
        Drink order = new Decaf();
        System.out.println("order description : " + order.getDescription());
        System.out.println("order price : " + order.cost());

        System.out.println("---------------加了调料的----------------");

        order = new Milk(order);// 加了牛奶
        order = new Chocolate(order);
        order = new Chocolate(order); // 加了两个巧克力
        System.out.println("order description : " + order.getDescription());
        System.out.println("order price : " + order.cost());
    }
}复制代码

程序输出:

order description : Decaf-3.0
order price : 3.0
---------------加了调料的----------------
order description : Chocolate-1.0 && Chocolate-1.0 && Milk-3.0 && Decaf-3.0
order price : 8.0复制代码

二、JavaIO中使用装饰者模式

因为Java I/O库须要不少性能的各类组合,若是这些性能都是用继承的方法实现的,那么每一种组合都须要一个类,这样就会形成大量性能重复的类出现,因此Java IO使用的是装饰者设计模式。

因此咱们能够定义本身的装饰者。

这里咱们定义一个流,这个流将读入的小写字母转换成大写字母。

UpperCaseInputStream代码以下:

/**
 * 本身定义的输入流  
 * 扩展FilterInputStream(这个类就是咱们的Decorator) 中间装饰者  
 * 因此咱们只要继承这个就能够扩展本身的输入流装饰者 
 */
public class UpperCaseInputStream extends FilterInputStream{

    protected UpperCaseInputStream(InputStream in) {  //这个InputStream就是咱们的Drink 类(超类)
        super(in);
    }

    // 实现两个read()方法,将大写转化成小写的读入

    //重写 至关于cost和description
    @Override
    public int read() throws IOException {
        int index = super.read(); //读取一个字节
        return index == -1 ? index : Character.toUpperCase((char)(index));  //小写转换成大写
    }

    //字节数组
    @Override
    public int read(byte[] b, int off, int len) throws IOException {
        int index = super.read(b, off, len);
        for(int i = 0; i < index; i++)
            b[i] = (byte)Character.toUpperCase((char)(b[i]));
        return index;
    }
}复制代码

测试一下使用这个类:

public class MyTest {

    public static void main(String[] args) throws IOException {
        InputStream in = new UpperCaseInputStream(new BufferedInputStream(new FileInputStream("/home/zxzxin/Java_Maven/DesignPatterns/src/main/java/decorator/java/in.txt")));// 将这个in.txt文件读入的内容转换成大写
        int len;
        while((len = in.read()) >= 0)
            System.out.print((char)(len));
        in.close();
    }
}复制代码

输出结果演示:

03_decorator_04.png

4、总结

优缺点:

  • 优势 : 装饰类和被装饰类能够独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式能够动态扩展一个实现类的功能。
  • 缺点 : 多层装饰比较复杂。

实际应用:  大多数状况下,装饰模式的实现都要比上面给出的示意性例子要简单。

  • 若是只有一个ConcreteComponent类,那么能够考虑去掉抽象的Component类(接口),把Decorator做为一个ConcreteComponent子类;
  •  若是只有一个ConcreteDecorator类,那么就没有必要创建一个单独的Decorator类,而能够把Decorator和ConcreteDecorator的责任合并成一个类。
相关文章
相关标签/搜索