• 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();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    两个设计原则

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

    装饰者模式


    什么是装饰者模式?

    假设需要一杯摩卡(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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    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();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    参考

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

    设计模式-装饰者模式

  • 相关阅读:
    2024华为OD机试真题-攀登者1-C++(C卷D卷)
    曾经,我对着AI客服喷了两分钟,它只回复了我的第一句话
    Spring MVC
    Java Web学习笔记6——盒子模型
    flink-sql所有表连接器-1.15
    目标检测——3D玩具数据集
    第1章 docker基础
    海关 瑞数5.5 找后缀加密入口解析
    项目复习:基于UDP的网络聊天室
    Shiro第一个程序官方快速入门程序Qucickstart详解教程
  • 原文地址:https://blog.csdn.net/qq_41398418/article/details/128014064