再谈装饰者模式

前言

装饰者模式的作用依旧很简单,与适配器模式较为相识,其作用也是扩展或者兼容原有的逻辑,之前的装饰者模式总结,总觉得似乎少了点什么,装饰者模式初探。这篇博客结合一个简单的生活实例来解释装饰者模式

实例

现有一个咖啡店,售卖各种咖啡。但是咖啡不简单是咖啡豆加水,有些顾客会要求加入糖和奶泡。

简单版本

似乎简单的继承就能满足上述的需求,下面贴出所有代码

1、coffee类

/**
 * autor:liman
 * comment:基础的咖啡类,白开水+咖啡豆,售价20
 */
public class Coffee {
    public String getMaterial(){
        return "咖啡豆,"+"白开水";
    }
    public int getPrice(){
        return 20;
    }
}

 2、咖啡加糖——CoffeeWithSugar

/**
 * autor:liman
 * comment:
 */
public class CoffeeWithSugar extends Coffee{

    @Override
    public String getMaterial() {
        return super.getMaterial()+",糖";
    }

    @Override
    public int getPrice() {
        return super.getPrice()+3;
    }
}

3、咖啡加糖加奶泡——CoffeeWithSugarAndMilk

/**
 * autor:liman
 * comment:
 */
public class CoffeeWithSugarAndMilk extends CoffeeWithSugar {
    @Override
    public String getMaterial() {
        return super.getMaterial()+",奶泡";
    }

    @Override
    public int getPrice() {
        return super.getPrice()+5;
    }
}

三者是很简单的继承关系,测试类如下:

public class CoffeeWithOutDecorateTest {

    public static void main(String[] args) {
        Coffee coffee = new Coffee();
        System.out.println(coffee.getMaterial()+":总售价:"+coffee.getPrice());

        CoffeeWithSugar coffeeWithSugar = new CoffeeWithSugar();
        System.out.println(coffeeWithSugar.getMaterial()+":总售价:"+ coffeeWithSugar.getPrice());

        CoffeeWithSugarAndMilk coffeeWithSugerAndMilk = new CoffeeWithSugarAndMilk();
        System.out.println(coffeeWithSugerAndMilk.getMaterial()+":总售价:"+coffeeWithSugerAndMilk.getPrice());
    }
}

运行结果:

再谈装饰者模式

这样表面上看似乎是满足了需求,如果有客户需要加入双份糖,双份奶泡,我们又得增加新的类,新的类可以继承上述的任意一个扩展类,这样扩展似乎并不合理。  

升级版本

1、这里我们将Coffee改成抽象类,毕竟Coffee会有很多变化,有拿铁,有卡布奇洛,而且有的客户需要加入糖和奶泡。不同的Coffee会有不同的表现,所以我们这里很有必要将其抽象化

/**
 * autor:liman
 * comment:
 */
public abstract class Coffee {

    public abstract String getMaterial();

    public abstract int getPrice();
}

 2、但是Coffee的基础还是有的,至少咖啡豆和白开水是少不了的。所以可以有一个基本的Coffee实现

/**
 * autor:liman
 * comment:
 */
public class BaseCoffee extends Coffee {
    @Override
    public String getMaterial() {
        return "咖啡豆,"+"白开水";
    }

    @Override
    public int getPrice() {
        return 20;
    }
}

3、下面才是关键,如果需要加入糖和奶泡,我们这里可以使用装饰者模式,可以先声明一个装饰器,但是装饰器不管怎么装饰都是依旧是Coffee,所以装饰器还是要继承基础的Coffee类。同时为了扩展增强原有的实例需要维护一个Coffee的属性。

/**
 * autor:liman
 * comment:装饰者的顶层类
 */
public class Decorator extends Coffee{

    private Coffee coffee;

    public Decorator(Coffee coffee) {
        this.coffee = coffee;
    }
    @Override
    public String getMaterial() {
        return this.coffee.getMaterial();
    }
    @Override
    public int getPrice() {
        return this.coffee.getPrice();
    }
}

 4、奶泡装饰器和糖类装饰器

奶泡装饰器

/**
 * autor:liman
 * comment: 奶泡装饰器
 */
public class MilkDecorator extends Decorator {
    public MilkDecorator(Coffee coffee) {
        super(coffee);
    }

    @Override
    public String getMaterial() {
        return super.getMaterial()+",奶泡";
    }

    @Override
    public int getPrice() {
        return super.getPrice()+10;
    }
}

 糖类装饰器

/**
 * autor:liman
 * comment:加入糖的装饰器
 */
public class SugarDecorator extends Decorator {
    public SugarDecorator(Coffee coffee) {
        super(coffee);
    }

    @Override
    public String getMaterial() {
        return super.getMaterial()+",糖";
    }

    @Override
    public int getPrice() {
        return super.getPrice()+5;
    }
}

5、测试实例

/**
 * autor:liman
 * comment:
 */
public class CoffeeDecoratorTest {

    public static void main(String[] args) {
        //买一杯基础的咖啡
        Coffee coffee = new BaseCoffee();
        System.out.println("咖啡原料:"+coffee.getMaterial()+"。售价:"+coffee.getPrice());

        //给咖啡只加奶泡
        Coffee milkCoffee = new MilkDecorator(coffee);
        System.out.println("咖啡原料:"+milkCoffee.getMaterial()+"。售价:"+milkCoffee.getPrice());

        //给coffee同时加入奶泡和糖
        Coffee milkAndSugarCoffee = new SugarDecorator(new MilkDecorator(coffee));
        System.out.println("咖啡原料:"+milkAndSugarCoffee.getMaterial()+"。售价:"+milkAndSugarCoffee.getPrice());
    }
}

这里加入奶泡和糖的时候,无需单独生成新的类,只需将现有的奶泡装饰器和糖装饰器组合加入即可,由于所有的装饰器都继承至Coffee类,所以构造函数传入的参数也可以直接传入已经存在的装饰实例。上述例子中所有类的关系图如下所示(图中去掉了CoffeeDecoratorTest)

再谈装饰者模式

总结

总体来看,升级版的实例似乎变成了横向扩展,而非一味的竖向扩展,相比适配器模式而言装饰者模式似乎又相似之处,都是扩展原有代码的逻辑。