JAVA设计模式之装饰模式

326 阅读4分钟

本文介绍23种设计模式之装饰模式。

定义

有时我们希望给某个对象而不是整个类添加一些功能,虽然使用继承是添加功能的一种方式,但是不够灵活,而且会导致子类增加了无用功能,耦合性太强。一种较为灵活的方式是将对象嵌入另一个对象中,通过该对象添加功能,称这个嵌入的对象为装饰。这个装饰与被装饰的对象接口一致,它将请求转发给被装饰对象,并在转发的前后进行额外操作。 装饰模式就是动态地给一个对象添加一些额外的职责。就增加功能来说,Decorator模式相比生成子类更为灵活。

描述

  • 模式名称:DECORATOR(装饰)
  • 类型:对象结构型模式
  • 意图:动态地给一个对象添加一些额外的职责。就增加功能来说, Decorator模式相比生成子类更为灵活。
  • 适用性:
    • 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
  • 效果:
  • 优点:
    • 比继承更灵活。
    • 避免在层次结构高层的类有太多的特征。
  • 缺点:
    • 多层装饰比较复杂。

下面通过一个例子,来深入理解装饰模式

有一家咖啡店,生意很火爆,想要更新订单系统,来满足目前的饮料供应需求。原先的设计如下:
有一个饮料的抽象类,所有的饮品都继承此类。类中包含了变量 description(描述),和方法 getDescription()获取描述信息,以及抽象方法cost()花费。子类要实现cost方法。

image.png

在原先饮品种类少的情况还可以,如果说为了提高市场竞争力和消费升级,要增加多种饮品,并按照加入的不同调味品收取不同的费用。要是还按照之前的方式创建子类实现cost,就会造成子类过多,并且如果某一种调味料价格有变动,需要修改的类就很多,不利于维护。

这时可能会说直接在饮料类中添加调味料变量,设置和获取是否有某种调味料的方法就好了,将饮料类中cost方法提供实现,用于判断调味料的价钱,子类只需 计算自己的价值,配置有哪种调料,并加上父类的就行

// 饮料类
public class Beverage {

   private String description;

   private double milkCost;

   private double soyCost;

   private double mochaCost;

   private double whipCost;


   private boolean hasMilk;

   private boolean hasSoy;

   private boolean hasMocha;

   private boolean hasWhip;

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public boolean isHasMilk() {
        return hasMilk;
    }

    public void setHasMilk(boolean hasMilk) {
        this.hasMilk = hasMilk;
    }

    public boolean isHasSoy() {
        return hasSoy;
    }

    public void setHasSoy(boolean hasSoy) {
        this.hasSoy = hasSoy;
    }

    public boolean isHasMocha() {
        return hasMocha;
    }

    public void setHasMocha(boolean hasMocha) {
        this.hasMocha = hasMocha;
    }

    public boolean isHasWhip() {
        return hasWhip;
    }

    public void setHasWhip(boolean hasWhip) {
        this.hasWhip = hasWhip;
    }
    
    public double cost(){
        double condimentCost = 0;
        if(hasMilk){
            condimentCost+=milkCost;
        }
        if(hasMocha){
            condimentCost+=mochaCost;
        }
        
        if(hasSoy){
            condimentCost+=soyCost;
        }
        
        if(hasWhip){
            condimentCost+= whipCost;
        }
        
        return condimentCost;
    }
}
public class DarkRoast extends Beverage {


    public DarkRoast() {
        setDescription("Most Excellent Dark Roast");
    }

    @Override
    public double cost() {
        return 2.3 + super.cost();
    }

}

这种方式存在几个问题:

  1. 调料价格变东需要修改现有代码
  2. 添加或删除新的调料,需要变动相应的变量和方法,以及 cost的计算。
  3. 如果有新的饮料不适合原有调料加入,子类依然有这些没用的调料。
  4. 如果想要双倍调料还需要修改cost并且添加变量。 由此看出这种方式明显违反了设计原则中的 开闭原则:对扩展开放,对修改关闭。在例子中,变化的是每种饮料的价格,价格需要根据调味品发生改变。那就先以饮料作为对象,用调味品对象装饰。

装饰模式类图结构

image.png

从中可以看出:

  1. 装饰者和被装饰着有相同的父类
  2. 可以使用一个或多个装饰类装饰对象
  3. 装饰对象可以替代被装饰类对象。
  4. 装饰对象可以在被装饰者的行为执行前后进行操作。

根据装饰者模式进行调整

  1. 先将饮料作为被封装者

image.png

2.用摩卡装饰饮料

image.png

3.再将被摩卡装饰的对象作为被装饰者,使用 奶泡进行装饰

image.png

下面来看下代码

装饰和被装饰的父类 
public abstract class  Beverage {

    public abstract String getDescription();

    public abstract double cost();
}

被装饰者类

public class DarkRoast extends Beverage {

    @Override
    public String getDescription() {
        return "DarkRoast";
    }

    @Override
    public double cost() {
        return 2.3 ;
    }

}

装饰者的父类

public abstract class CondimentDecorator extends Beverage {
    private Beverage beverage;

    public CondimentDecorator (Beverage beverage){
        this.beverage = beverage;
    }

    public Beverage getBeverage() {
        return beverage;
    }

}

装饰者实现类

public class Mocha extends CondimentDecorator {

    public Mocha(Beverage beverage){
        super(beverage);
    }

    @Override
    public String getDescription() {

        return getBeverage().getDescription()+" Mocha";
    }

    @Override
    public double cost() {
        return getBeverage().cost()+.20;
    }
}



public class Whip extends CondimentDecorator {

    public Whip(Beverage beverage){
        super(beverage);
    }

    @Override
    public String getDescription() {

        return getBeverage().getDescription()+" Whip";
    }

    @Override
    public double cost() {
        return getBeverage().cost()+.10;
    }
}

调用情况

public static void main(String[] args) {
    Beverage beverage = new DarkRoast();
    System.out.println(beverage.getDescription()+" $ "+beverage.cost());

    Mocha mocha = new Mocha(beverage);
    System.out.println(mocha.getDescription()+" $ "+mocha.cost());

    Whip whip = new Whip(mocha);
    System.out.println(whip.getDescription()+" $ "+whip.cost());
}

打印情况
DarkRoast $ 2.3
DarkRoast Mocha $ 2.5
DarkRoast Mocha Whip $ 2.6

Java I/O的装饰模式

java.io包内的类看起来比较多,其实就是利用了装饰模式

image.png

其中:InputStream是装饰类和被装饰类的父类;FileInputStream,StringBufferInputStream,ByteArrayInputStream是被装饰类, FilterInputStream是装饰类的父类,BufferedInputStream,LineNumberInputStream,DataInputStream是装饰类。