Java设计模式之装饰模式详解

装饰者模式是动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。

假设咱们有一个需求,是给一家饮料店作一个计算各类饮料价格的功能。听起来很简单,咱们建立一个抽象父类Beverages,description用来描述饮料名字,price方法用来计算饮料的价格。ide

public abstract class Beverages {
    private String description;

    public String getDescription() {
        return description;
    }

    public abstract double price();
}

每种饮料咱们都建立一个子类去继承父类,赋值给description,而且重写price方法来设置本身的价格。看上去很完美,咱们运行一下看看结果。函数

public class Coffee extends Beverages {

    public Coffee(String description){
        this.description = description;
    }

    @Override
    public String getDescription() {
        return description;
    }

    @Override
    public double price() {
        return 3.5;
    }
}

public static void main(String[] args){
    Coffee coffee = new Coffee("咖啡");
    System.out.println(coffee.getDescription());
    System.out.println(coffee.price());
}

结果:
咖啡
3.5this


可是问题来了,饮料店里不单单只有一种饮料,还有可乐、七喜、奶茶其余各类各样的饮料,难道咱们要每一个都建立一个子类吗?好吧,就算你以为几十种饮料还不算多,那若是各类饮料直接再进行搭配呢?咖啡加牛奶、咖啡加巧克力、加糖、不加糖,由于各类配料的不一样价格和描述也不一样,个人天呀,难道要建立几百个类吗?这个时候咱们忽然想到了不是能够用继承来解决这个问题吗?那咱们就来试一下。设计


咱们改造一下Beverages类,把要加的配料用boolean值声明,若是须要添加配料就调用set方法设置为true,在price方法中计算的时候来判断哪一些配料添加了须要计算价格。code

public class Beverages {
    public String description;

    public boolean milk;
    public boolean sugar;

    public double milkPrice = 1.5;
    public double sugarPrice = 0.5;

    public boolean isMilk() {
        return milk;
    }

    public void setMilk(boolean milk) {
        this.milk = milk;
    }

    public boolean isSugar() {
        return sugar;
    }

    public void setSugar(boolean sugar) {
        this.sugar = sugar;
    }

    public String getDescription() {
        return description;
    }

    public double price(){
        double basePrice = 0.0;
        if (isMilk()){
            basePrice += milkPrice;
        }
        if (isSugar()){
            basePrice += sugarPrice;
        }
        return basePrice;
    }
}

而后咱们在子类计算价格的时候加上父类中计算好的配料的价格。对象

public class CoffeeWithMilk extends Beverages {

    public CoffeeWithMilk(String description){
        this.description = description;
    }

    @Override
    public double price() {
        return 3.0 + super.price();
    }
}

咱们运行看一下,咖啡加牛奶加糖,结果没有问题。继承

CoffeeWithMilk coffeeWithMilk = new CoffeeWithMilk("咖啡加牛奶加糖");
coffeeWithMilk.setMilk(true);
coffeeWithMilk.setSugar(true);
System.out.println(coffeeWithMilk.getDescription());
System.out.println(coffeeWithMilk.price());

结果:
咖啡加牛奶加糖
5.0ip


这样一来咱们解决了新建无数个子类的问题,可是咱们发现单纯继承的作法仍是有太多弊端,好比说若是咱们想要新添加配料,咱们得修改在父类Beverages中添加新的调料字段,还要修改price方法,这严重违反了开发-关闭的设计原则,类应该对扩展开发,对修改关闭。并且有些饮料和配料是没办法搭配的,例如啤酒加糖,可是子类仍是会继承到这些配料,而且若是是要同一份配料要加双份又该怎么改呢?因此单纯继承的方法仍是不行。这时候咱们就要使用到装饰者模式。开发


首先咱们建立抽象父类Beverages,这个父类只是饮料的父类,不是配料的父类。咱们建立一个Cola类直接继承它。get

public abstract class Beverages {
    public String description;

    public String getDescription() {
        return description;
    }

    public abstract double price();
}

public class Cola extends Beverages {

    public Cola(String description) {
        this.description = description;
    }

    @Override
    public double price() {
        return 2.0;
    }
}

如今就缺配料的部分的代码了,咱们再建立一个配料的抽象类Seasonings,咱们让它继承Berverages类,而且声明了一个Berverages的引用和抽象的getDescription方法,这里咱们稍后再做解释。

public abstract class Seasonings extends Beverages{
    public Beverages beverages;
    public abstract String  getDescription();
}

接下来咱们看看配料的实现类怎么写。咱们让他继承了父类Seasonings,构造函数接收父类Beverages类型,其实也就是要被装饰的对象,也就是各类各样须要加配料的饮料。而后咱们重写了getDescription,咱们在传进来的那个Beverages对象的基础上添加名字,price方法也是同理。这样其实就在给传进来的对象外面作了一层装饰,也就是给饮料添加了配料。

public class Ice extends Seasonings {

    public Ice(Beverages beverages) {
        this.beverages = beverages;
    }

    @Override
    public String getDescription() {
        return beverages.getDescription() + "加冰";
    }

    @Override
    public double price() {
        return beverages.price() + 0.2;
    }
}

运行一下,看看结果。没有问题,能够搭配成功。

Cola cola = new Cola("可乐");

Beverages ice = new Ice(cola);
System.out.println(ice.getDescription());
System.out.println(ice.price());

结果:
可乐加冰
2.2


咱们还想再添加别的配料,再建立一个新的配料。

public class Sugar extends Seasonings {

    public Sugar(Beverages beverages) {
        this.beverages = beverages;
    }

    @Override
    public String getDescription() {
        return beverages.getDescription() + "加糖";
    }

    @Override
    public double price() {
        return 0.5 + beverages.price();
    }
}

咱们在原来搭配好的基础上再进行装饰,结果也是没有问题的。这个时候就要提到为何咱们的Seasonings类要继承Beverages类了,由于每一个配料的实现类里有一个Beverages类型的引用,这样咱们才能够对它的子类进行装饰,咱们让Seasonings也继承Beverages那它的子类也是Beverages类型,咱们就能够想装饰几层就装饰几层。

Beverages ice = new Ice(cola);
System.out.println(ice.getDescription());
System.out.println(ice.price());

Beverages sugar = new Sugar(ice);
System.out.println(sugar.getDescription());
System.out.println(sugar.price());

结果:
可乐加冰
2.2
可乐加冰加糖
2.7


在Java的类库中就有不少实际应用到了装饰模式,好比BufferedInputStream就能够用来装饰FileInputStream,提供更增强大的功能。


总结:装饰模式就是装饰者和被装饰者都具备相同的超类,装饰者拿到被装饰者的引用以后,在调用被装饰者的方法的同时再加上本身的新功能,从而实现了功能的增长,也不须要修改原来的代码。并且由于是相同的超类,因此能够装饰不少层。

相关文章
相关标签/搜索