Head First设计模式(阅读笔记)-03.装饰者模式

星巴兹咖啡

咖啡存在许多的种类,同时也有不同的调料。此时用户可以单点咖啡,也可以点咖啡+调料,请计算费用(这里咖啡和调料都属于Drink的一类)


简单实现


方案1

每出现一种组合就实现一个类,但是每次增加一个咖啡种类或者一个新的调料,类的数量就会倍增,出现类爆炸!


在这里插入图片描述

方案2

具体修改如下:

  • 在基类中加上每个调料的实例变量,并且将其设置为boolean类型表示是否加入
  • 基类的cost方法不再是抽象方法,而是计算加入所有调料后的价格(为什么是计算所有调料???),子类会调用基类的cost方法
  • hasxxxsetxxx方法由于取得和设置调料的bool

这样大大减少了类的数量,但是该方案会出现以下问题:

  • 调整价钱就要修改代码
  • 增加或者删除调料种类时,就要加上新方法并且修改基类的cost方法
  • 某些新饮料(子类)并不需要某些调料,但是仍要继承这些不需要的方法,比如hashSoy

在这里插入图片描述

public class Drink{
    String desc;
    // mikeCost、soyCost的实例变量
    // mike、soy的getter和setter方法
    public double cost(){
        float cost = 0.0;
        if(hasMilk()){
            cost += milkCost;
        }
        if(hasSoy()){
            cost += soyCost;
        }
        return cost;
    }
}
public class DarkRoast extends Drink{
    public DarkRoast(){
        desc = "好喝";
    }
    public double cost(){
        return 2.0 + super.cost();
    }
}
两个设计原则

  • 利用组合和委托代替继承:
    • 继承设计子类的行为是在编译时静态决定(即所有子类都会继承到相同的行为),而组合扩展对象行为时可以在运行时动态地扩展
    • 通过动态组合对象可以在不修改代码的前提下添加新功能(下面提到的开闭原则)
  • 尽量遵循开闭原则:
    • 类应该对扩展开发,对修改关闭
    • 不需要每个地方都遵循该原则,因为会让代码变得复杂

装饰者模式


什么是装饰者模式?

假设需要一杯摩卡(Mocha)+奶泡(whip)的深焙咖啡(DarkRoast),具体做法和计算价格的过程如下:

  • 拿到一个DarkRoast对象
  • 使用Mocha对象装饰它
  • 使用Whip对象装饰它
  • 调用cost方法,并依赖委托将调料价钱加上

在这里插入图片描述


  • 装饰者可以在被装饰者的行为前/后加上自己的行为(Whip是装饰者,则Mocha是被装饰者,其他依次类推)

  • 装饰者模式可以动态将责任附加到对象上,即扩展功能时更具弹性

  • 装饰者和被装饰者具有相同的父类(因为装饰者可能变为被装饰者,被装饰者也可能变为装饰者)

代码实现

// 需要被继承的公共父类
public abstract class Drink{
    String desc = "Unknown";
    public String getDesc(){
        return desc;
    }
    public abstract double cost();
}

// 抽象装饰者类(目的在于让装饰者类必须实现getDesc方法)
public abstract class CondimentDecorator extends Drink{
    public abstract String getDesc();
}

// Espresso类,即被装饰者类
public class Soy extends Drink{
    public Soy(){
        desc = "Soy"
    }
    public double cost(){
        return 2.0;
    }
}

// Mocha类,即装饰者类
public class Mochas extends CondimentDecorator{
    Drink drink;  // 组合
    public Mochas(Drink drink){
        this.drink = drink;
    }
    public String getDesc(){
        return drink.getDesc() + ", Mocha";
    }
    public double cost(){
        // 1.0是Mocha自己的价格
        return 1.0 + drink.cost();
    }
}

// 测试
public class CoffeeStore{
    public static void main(String[] args){
        // 点一杯加了豆浆的摩卡
        Drink soy = new Soy();
        Drink Mocha = new Mocha(espresso);
    }
}

Java I/O

Java I/O类中,同样使用了装饰者模式,但是也是有缺点的,比如会出现大量的小类

在这里插入图片描述


编写自己的Java I/O装饰者

编写一个装饰者,把输入流中的大写都转为小写


import java.io.*;
// FilterInputStream是抽象装饰者
public class LowerCaseInputStream extends FilterInputStream{
    public LowerCaseInputStream(InputStream in){
        super(in);
    }
    // 针对字节
    public int read() throws IOException{
        int c = super.read();
        return (c == -1 ? c : Character.toLowerCase((char)c));
    }
    // 针对字节数组
    public int read(byte[] b, int offset, int len) throws IOException{
        int result = super.read(b, offset, len);
        for(itn i = offset; i < offset + result; i++){
            b[i] = (byte)Character.toLowerCase((char)b[i]);
        }
        return result;
    }
}

// 测试
public class InputTest{
    public static void main(String[] args) throws IOException{
        int c;
        try{
            // 先用BufferdInputStream修饰FileInputStream
            // 再用LowerCaseInputStream修饰BufferdInputStream
            InputStream in = new LowerCaseInputStream(
                new BufferdInputStream(new FileInputStream("test.txt")));
            while((c = in.read()) >= 0){
                System.out.print((char)c);
            }
            in.close();
        }catch(IOException e){
            e.printStackTrace();
        }
    }
}

参考

Head First 设计模式-装饰者模式

设计模式-装饰者模式

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值