装饰器模式

想一个场景

我们在Service里经常会去取个人信息,为了不每次都通过DAO从数据库里面读数据,那我们会在数据库查询之前加上一层缓存读取,返回同样结构的数据且不改变查询逻辑本身,这里添加缓存就是在使用装饰器模式

模式介绍

在模式介绍之前,先声明一条简短的程序设计原则

开闭原则

类应该对扩展开放,对修改关闭

装饰器模式是在对已有逻辑的不修改的情况下,在外层包装一层逻辑,使包装逻辑与原始逻辑去耦

我们确实可以使用子类继承父类,然后在子类中添加包装逻辑,但这儿有一点很关键,就是子类中的包装逻辑与父类逻辑耦合,无法拆分,在另外的子类中需要同样的包装逻辑时,又需要再写一遍。

这就很成问题,同样的代码在不同的地方多次出现,这就意味着这一块儿代码需要改进了。

类图

装饰器模式

  • ConcreteComponent:要动态的加上包装逻辑的对象
  • ConcreteDecoratorA/ConcreteDecoratorB:具体包装逻辑的装饰器

示例

此处我使用了一个售卖咖啡的场景,在用户选择一份咖啡加多份调料的时候,来计算总价格,并输出已点的内容。

  • 费用计算接口

public interface CostCalculator {
    String getDescription();
    int cost();
}
  • 调味品接口
public interface Condiment extends CostCalculator {
    int getPrice();
}
  • 咖啡虚类
public abstract class Coffee implements CostCalculator{

    String description = "未知的类型";

    public String getDescription(){
        return description;
    }
}
  • 具体咖啡类:浓缩咖啡
public class Espresso extends Coffee {

    public Espresso(){
        description = "浓缩咖啡";
    }

    /**
     * 单售价格
     * @return
     */
    @Override
    public int cost() {
        return 14;
    }
}
  • 具体咖啡类:混合咖啡
public class HouseBlend extends Coffee {

    public HouseBlend(){
        description = "混合咖啡";
    }

    /**
     * 单售价格
     * @return
     */
    @Override
    public int cost() {
        return 11;
    }
}
  • 具体调味品:巧克力
public class Chocolate implements Condiment {

    private CostCalculator costCalculator;

    /**
     * 巧克力的价格
     */
    private int price = 5;

    public Chocolate(CostCalculator costCalculator){
        this.costCalculator = costCalculator;
    }

    @Override
    public String getDescription() {
        return costCalculator.getDescription()+",巧克力";
    }

    /**
     *
     * @return
     */
    @Override
    public int cost() {
        return costCalculator.cost()+getPrice();
    }

    @Override
    public int getPrice() {
        return price;
    }
}
  • 具体调味品:牛奶
public class Milk implements Condiment {

    private CostCalculator costCalculator;

    /**
     * 牛奶的价格
     */
    private int price = 6;

    public Milk(CostCalculator costCalculator){
        this.costCalculator = costCalculator;
    }

    @Override
    public String getDescription() {
        return costCalculator.getDescription()+",牛奶";
    }

    @Override
    public int cost() {
        return costCalculator.cost()+getPrice();
    }

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

  • 单元测试类
public class CoffeeOrderTest {

    @Test
    public void test1(){

        CostCalculator espresso  = new Espresso();
        //一杯浓缩咖啡的价格
        printPrice(espresso.cost());
        printDes(espresso.getDescription());
        //加上牛奶之后的价格
        espresso = new Milk(espresso);
        printPrice(espresso.cost());
        printDes(espresso.getDescription());

        //一杯混合咖啡
        CostCalculator houseBlend = new HouseBlend();
        printPrice(houseBlend.cost());
        printDes(houseBlend.getDescription());
        //在加上巧克力后的价格
        houseBlend = new Chocolate(houseBlend);
        printPrice(houseBlend.cost());
        printDes(houseBlend.getDescription());
        //加上牛奶之后的价格
        houseBlend = new Milk(houseBlend);
        printPrice(houseBlend.cost());
        printDes(houseBlend.getDescription());


    }

    private void printPrice(int price){
        System.out.println("价格:"+price+"元");
    }

    private void printDes(String des){
        System.out.println("包含:"+des);
    }
}
  • 控制台输出

装饰器模式

结语

  • 使用装饰器模式,可以把继承的树形拓展转换为接口的水平拓展

  • 就水平拓展能力来看,装饰者模式也存在一个明显的弱点:需要定义大量的小装饰类,这可能会给使用者带来一定的困扰。

  • 由于装饰器的装饰次数无限制,所以需要工厂模式和生成器模式来对装饰器的使用进行限制

附属1:Java内置的jar包中的装饰器模式的应用

在java.io包里,对InputStream,OutputStream几乎都是用的装饰器模式来做拓展的

javaIO包

我也可以利用装饰器模式来拓展InputStream

将输入流中的英文字符转换为小写
public class LowerCaseInputStream extends FilterInputStream {
    /**
     * Creates a <code>FilterInputStream</code>
     * by assigning the  argument <code>in</code>
     * to the field <code>this.in</code> so as
     * to remember it for later use.
     *
     * @param in the underlying input stream, or <code>null</code> if
     *           this instance is to be created without an underlying stream.
     */
    protected LowerCaseInputStream(InputStream in) {
        super(in);
    }

    public int read() throws IOException {
        int c = super.read();
        return c == -1 ? c : Character.toLowerCase(c);
    }

    public int read(byte[] b,int offset,int len) throws IOException{
        int result = super.read(b,offset,len);
        for (int i = offset;i<offset+result;i++){
            b[i] = (byte)Character.toLowerCase(b[i]);
        }
        return result;
    }
}

测试类

public class LowerCaseInputStreamTest {

    @Test
    public void test(){
        int c = 0;
        try{
            LowerCaseInputStream inputStream = new LowerCaseInputStream(
                                                    new BufferedInputStream(
                                                            new FileInputStream("/data/test.txt")));
            while((c = inputStream.read())>=0){
                System.out.print((char) c);
            }
            inputStream.close();
        }catch (IOException e){
            e.printStackTrace();
        }
    }
}

读取文件

读取文件

测试结果

测试结果

附属2:使用工厂模式和策略模式来优化装饰者模式

在上面使用装饰器的时候已经提到过,大量的小装饰类会淹没使用者。而且对于咖啡的外层使用者来说,如果他需要多次去拿一杯摩卡咖啡,那么他就不用关注摩卡咖啡是通过哪些装饰器产生的。

例如:

抽象工厂类

public abstract class AbstractCoffeeFactory {
    public abstract CostCalculator getCoffee();
}

摩卡咖啡工厂

public class MochaCoffeeFactory extends AbstractCoffeeFactory{

    private CostCalculator mocha;

    public MochaCoffeeFactory(){
        init();
    }

    public CostCalculator getCoffee(){
        //加巧克力
        mocha = new Chocolate(mocha);
        //加牛奶
        mocha = new Milk(mocha);
        return mocha;
    }

    private void init(){
        mocha = new Espresso();
    }
}

咖啡工厂选择器(策略模式)

public class CoffeeSelector {

    private Map<Integer,AbstractCoffeeFactory> coffeeFactoryMap;

    public CoffeeSelector(){
        init();
    }

    public CostCalculator select(int type){
        return coffeeFactoryMap.get(type).getCoffee();
    }

    private void init(){
        coffeeFactoryMap = new HashMap<>();
        coffeeFactoryMap.put(CoffeeType.MOCHA,new MochaCoffeeFactory());
    }

    public @interface CoffeeType{
        int MOCHA = 0;
    }
}

测试类

/**
 * 咖啡订单测试
 */
public class CoffeeOrderTest {

    /**
     * 装饰器模式
     */
    @Test
    public void test1(){
        CostCalculator espresso  = new Espresso();
        //一杯浓缩咖啡的价格
        printPrice(espresso.cost());
        printDes(espresso.getDescription());
        //加上牛奶之后的价格
        espresso = new Milk(espresso);
        printPrice(espresso.cost());
        printDes(espresso.getDescription());

        //一杯混合咖啡
        CostCalculator houseBlend = new HouseBlend();
        printPrice(houseBlend.cost());
        printDes(houseBlend.getDescription());
        //在加上巧克力后的价格
        houseBlend = new Chocolate(houseBlend);
        printPrice(houseBlend.cost());
        printDes(houseBlend.getDescription());
        //加上牛奶之后的价格
        houseBlend = new Milk(houseBlend);
        printPrice(houseBlend.cost());
        printDes(houseBlend.getDescription());

    }

    /**
     * 添加工厂模式
     */
    @Test
    public void test2(){
        CoffeeSelector selector = new CoffeeSelector();
        CostCalculator mocha = selector.select(CoffeeSelector.CoffeeType.MOCHA);
        printPrice(mocha.cost());
        printDes(mocha.getDescription());
    }

    private void printPrice(int price){
        System.out.println("价格:"+price+"元");
    }

    private void printDes(String des){
        System.out.println("包含:"+des);
    }


}

测试结果

工厂模式

结果没有发生变化

转载于:https://my.oschina.net/u/3638962/blog/1528899

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值