【设计模式——学习笔记】23种设计模式——装饰器模式Decorator(原理讲解+应用场景介绍+案例介绍+Java代码实现)

简介: 【设计模式——学习笔记】23种设计模式——装饰器模式Decorator(原理讲解+应用场景介绍+案例介绍+Java代码实现)

生活案例

咖啡厅 咖啡定制案例

在咖啡厅中,有多种不同类型的咖啡,客户在预定了咖啡之后,还可以选择添加不同的调料来调整咖啡的口味,当客户点了咖啡添加了不同的调料,咖啡的价格需要做出相应的改变。

要求:程序实现具有良好的拓展性、改动方便、维护方便

【方案一】

写一个抽象类Drink,然后将所有咖啡和调料组合形成多个类来继承抽象类,缺点:当增加一个单品咖啡,或者调味,类的数量就会大增,产生类爆炸问题

【方案二】

分析:

  • 可以控制类的数量,不至于造成很多的类
  • 增加或者删除调料种类时,代码的维护量很大
  • 如果同样一种调料可以点多份时,可以将 hasMilk 返回一个对应int类型的数据来表示调料的份数

装饰者模式介绍

介绍

  • 动态的将新功能附加到对象上。在对象功能扩展方面,它比继承更好,装饰者模式也体现了开闭原则(ocp)
  • 假如有一块蛋糕,如果加上奶油,就变成了奶油蛋糕;再加上草莓,就变成了草莓奶油蛋糕。整个过程就是在不断装饰蛋糕的过程。根据装饰者模式编写的程序的对象与蛋糕十分相似。首先有一个相当于蛋糕的对象,然后像不断地装饰蛋糕一样地不断地对其增加功能,它就变成了使用目的更加明确的对象。

出场角色

  • Component(主体,被装饰对象):增加功能时的核心角色,定义了接口(API)
  • ConcreteComponent(具体主体):实现了Component角色所定义的接口
  • Decorator(装饰者):该角色具有与Component角色相同的接口(API),在它内部保存了Component角色
  • ConcreteDecorator( 具体的装饰者)

案例实现

案例一(咖啡厅问题)

类图

代码实现

【被装饰主体】

package com.atguigu.decorator;
public abstract class Drink {
   /**
    * 描述
    */
   public String des;
   /**
    * 价格
    */
   private float price = 0.0f;
   public String getDes() {
      return des;
   }
   public void setDes(String des) {
      this.des = des;
   }
   public float getPrice() {
      return price;
   }
   public void setPrice(float price) {
      this.price = price;
   }
   /**
    * 计算费用的抽象方法,需要子类来实现
    * @return
    */
   public abstract float cost();
}

【缓冲类:整合所有咖啡的共同点,这个类不一定要写,要结合实际情况】

package com.atguigu.decorator;
public class Coffee  extends Drink {
   @Override
   public float cost() {
      return super.getPrice();
   }
}

【单品咖啡:意大利咖啡】

package com.atguigu.decorator;
public class Espresso extends Coffee {
   public Espresso() {
      setDes(" 意大利咖啡 ");
      // 初始化意大利咖啡的价格
      setPrice(6.0f);
   }
}

【单品咖啡:美式咖啡】

package com.atguigu.decorator;
public class LongBlack extends Coffee {
   public LongBlack() {
      setDes(" longblack ");
      setPrice(5.0f);
   }
}

【单品咖啡:浓咖啡】

package com.atguigu.decorator;
public class ShortBlack extends Coffee{
   public ShortBlack() {
      setDes(" shortblack ");
      setPrice(4.0f);
   }
}

【装饰者】

package com.atguigu.decorator;
/**
 * 装饰物,继承了Drink,还聚合了Drink
 */
public class Decorator extends Drink {
   private Drink obj;
   /**
    * 聚合Drink
    * @param obj
    */
   public Decorator(Drink obj) {
      this.obj = obj;
   }
   @Override
   public float cost() {
      // getPrice 自己价格 + 咖啡的价格
      return super.getPrice() + obj.cost();
   }
   /**
    * 输出信息
    * @return
    */
   @Override
   public String getDes() {
      // obj.getDes() 输出被装饰者的信息
      return des + " " + getPrice() + " && " + obj.getDes();
   }
}

【具体装饰者:巧克力】

package com.atguigu.decorator;
/**
 * 具体的Decorator, 这里就是调味品
 */
public class Chocolate extends Decorator {
   public Chocolate(Drink obj) {
      super(obj);
      setDes(" 巧克力 ");
      // 调味品 的价格
      setPrice(3.0f); 
   }
}

【具体装饰者:牛奶】

package com.atguigu.decorator;
public class Milk extends Decorator {
   public Milk(Drink obj) {
      super(obj);
      setDes(" 牛奶 ");
      setPrice(2.0f);
   }
}

【具体装饰者:豆浆】

package com.atguigu.decorator;
public class Soy extends Decorator{
   public Soy(Drink obj) {
      super(obj);
      setDes(" 豆浆  ");
      setPrice(1.5f);
   }
}

【主类】

package com.atguigu.decorator;
public class CoffeeBar {
   public static void main(String[] args) {
      System.out.println("============== 订单1 =============");
      // 装饰者模式下的订单:2份巧克力+一份牛奶的LongBlack
      // 1. 点一份 LongBlack
      Drink order = new LongBlack();
      System.out.println("费用=" + order.cost());
      System.out.println("描述=" + order.getDes());
      System.out.println();
      // 2.加入一份牛奶
      order = new Milk(order);
      System.out.println("order 加入一份牛奶 费用 =" + order.cost());
      System.out.println("order 加入一份牛奶 描述 = " + order.getDes());
      System.out.println();
      // 3.加入一份巧克力
      order = new Chocolate(order);
      System.out.println("order 加入一份牛奶 加入一份巧克力 费用 =" + order.cost());
      System.out.println("order 加入一份牛奶 加入一份巧克力 描述 = " + order.getDes());
      System.out.println();
      // 4.加入一份巧克力
      order = new Chocolate(order);
      System.out.println("order 加入一份牛奶 加入2份巧克力 费用 =" + order.cost());
      System.out.println("order 加入一份牛奶 加入2份巧克力 描述 = " + order.getDes());
      System.out.println();
   }
}

【运行】

============== 订单1 =============
费用=5.0
描述= longblack 
order 加入一份牛奶 费用 =7.0
order 加入一份牛奶 描述 =  牛奶  2.0 &&  longblack 
order 加入一份牛奶 加入一份巧克力 费用 =10.0
order 加入一份牛奶 加入一份巧克力 描述 =  巧克力  3.0 &&  牛奶  2.0 &&  longblack 
order 加入一份牛奶 加入2份巧克力 费用 =13.0
order 加入一份牛奶 加入2份巧克力 描述 =  巧克力  3.0 &&  巧克力  3.0 &&  牛奶  2.0 &&  longblack 

咖啡样式拓展代码实现

只需要新增一个单品咖啡类,就可以购买了,拓展性非常强大

【新增单品咖啡:无因咖啡】

package com.atguigu.decorator;
public class DeCaf extends Coffee {
   public DeCaf() {
      setDes(" 无因咖啡 ");
      setPrice(1.0f);
   }
}

【主类】

package com.atguigu.decorator;
public class CoffeeBar {
   public static void main(String[] args) {
      System.out.println("============== 订单2 =============");
      Drink order2 = new DeCaf();
      System.out.println("order2 无因咖啡 费用 =" + order2.cost());
      System.out.println("order2 无因咖啡 描述 = " + order2.getDes());
      System.out.println();
      order2 = new Milk(order2);
      System.out.println("order2 无因咖啡 加入一份牛奶 费用 =" + order2.cost());
      System.out.println("order2 无因咖啡 加入一份牛奶 描述 = " + order2.getDes());
      System.out.println();
   }
}

【运行】

============== 订单2 =============
order2 无因咖啡 费用 =1.0
order2 无因咖啡 描述 =  无因咖啡 
order2 无因咖啡 加入一份牛奶 费用 =3.0
order2 无因咖啡 加入一份牛奶 描述 =  牛奶  2.0 &&  无因咖啡 
Process finished with exit code 0

案例二

类图

代码实现

【抽象主体】

package com.atguigu.decorator.Sample;
public abstract class Display {
    /**
     * 获取横向字符数(抽象方法,需要子类去实现)
     * @return
     */
    public abstract int getColumns();
    /**
     * 获取纵向行数(抽象方法,需要子类去实现)
     * @return
     */
    public abstract int getRows();
    /**
     * 获取第row行的字符串(抽象方法,需要子类去实现)
     * @param row
     * @return
     */
    public abstract String getRowText(int row);
    /**
     * 显示所有行的字符串
     */
    public void show() {
        // 遍历行数
        for (int i = 0; i < getRows(); i++) {
            // 获取改行的字符串来打印出来
            System.out.println(getRowText(i));
        }
    }
}

【具体主体】

package com.atguigu.decorator.Sample;
/**
 * 该类用来显示单行字符串
 */
public class StringDisplay extends Display {
    /**
     * 要显示的字符串
     */
    private String string;
    /**
     * 构造方法
     *
     * @param string 要显示的字符串
     */
    public StringDisplay(String string) {
        this.string = string;
    }
    @Override
    public int getColumns() {
        // 字符数
        return string.getBytes().length;
    }
    @Override
    public int getRows() {
        // 行数是1
        return 1;
    }
    /**
     * 只有第0行可以获取到字符串,其他都是空
     * @param row
     * @return
     */
    @Override
    public String getRowText(int row) {
        // 仅当row为0时返回值
        if (row == 0) {
            return string;
        } else {
            return null;
        }
    }
}

【抽象装饰者】

package com.atguigu.decorator.Sample;
/**
 * 装饰者抽象类,注意要继承抽象主体,并聚合抽象主体
 */
public abstract class Border extends Display {
    /**
     * 表示被装饰物
     */
    protected Display display;
    protected Border(Display display) {
        // 在生成实例时通过参数指定被装饰物
        this.display = display;
    }
}

【具体修饰者1】

package com.atguigu.decorator.Sample;
/**
 * 在字符串的左右两侧添加边框
 */
public class SideBorder extends Border {
    /**
     * 表示装饰边框的字符
     */
    private char borderChar;
    /**
     * 通过构造函数指定Display和装饰边框字符
     * @param display
     * @param ch
     */
    public SideBorder(Display display, char ch) {
        super(display);
        this.borderChar = ch;
    }
    /**
     * 字符数为字符串字符数加上两侧边框字符数
     * @return
     */
    public int getColumns() {
        return 1 + display.getColumns() + 1;
    }
    /**
     * 行数即被装饰物的行数
     * @return
     */
    public int getRows() {
        // 在字符串的两侧添加字符并不会增加行数,所以直接返回主体的行数即可
        return display.getRows();
    }
    /**
     * 指定的那一行的字符串为被装饰物的字符串加上两侧的边框的字符
     * @param row
     * @return
     */
    public String getRowText(int row) {
        return borderChar + display.getRowText(row) + borderChar;
    }
}

【具体装饰者2】

package com.atguigu.decorator.Sample;
/**
 * 在字符串的上下左右都加上装饰框
 */
public class FullBorder extends Border {
    public FullBorder(Display display) {
        super(display);
    }
    public int getColumns() {
        // 字符数为被装饰物的字符数加上两侧边框字符数
        return 1 + display.getColumns() + 1;
    }
    public int getRows() {
        // 行数为被装饰物的行数加上上下边框的行数
        return 1 + display.getRows() + 1;
    }
    /**
     * 指定的那一行的字符串
     *
     * @param row
     * @return
     */
    public String getRowText(int row) {
        if (row == 0) {                                                 // 上边框
            return "+" + makeLine('-', display.getColumns()) + "+";
        } else if (row == display.getRows() + 1) {                      // 下边框
            return "+" + makeLine('-', display.getColumns()) + "+";
        } else {                                                        // 其他边框
            return "|" + display.getRowText(row - 1) + "|";
        }
    }
    /**
     * 生成一个重复count次字符ch的字符串
     *
     * @param ch
     * @param count
     * @return
     */
    private String makeLine(char ch, int count) {
        StringBuffer buf = new StringBuffer();
        for (int i = 0; i < count; i++) {
            buf.append(ch);
        }
        return buf.toString();
    }
}

【主类】

package com.atguigu.decorator.Sample;
public class Main {
    public static void main(String[] args) {
        Display b1 = new StringDisplay("Hello, world.");
        Display b2 = new SideBorder(b1, '#');
        Display b3 = new FullBorder(b2);
        b1.show();
        b2.show();
        b3.show();
        Display b4 =
                new SideBorder(
                        new FullBorder(
                                new FullBorder(
                                        new SideBorder(
                                                new FullBorder(
                                                        new StringDisplay("你好,世界。")
                                                ),
                                                '*'
                                        )
                                )
                        ),
                        '/'
                );
        b4.show();
    }
}

【运行】

Hello, world.
#Hello, world.#
+---------------+
|#Hello, world.#|
+---------------+
/+------------------------+/
/|+----------------------+|/
/||*+------------------+*||/
/||*|你好,世界。|*||/
/||*+------------------+*||/
/|+----------------------+|/
/+------------------------+/
Process finished with exit code 0

装饰着模式在IO流源码的应用

  • InputStream 是抽象类, 类似我们前面讲的 Drink
  • FileInputStream 是 InputStream 子类,类似我们前面的 DeCaf, LongBlack
  • FilterInputStream 是 InputStream 子类:类似我们前面 的 Decorator 修饰者

DataInputStream 是 FilterInputStream 子类,具体的修饰者,类似前面的 Milk, Soy 等

FilterInputStream 类 有 protected volatile InputStream in; 即聚合了被装饰者

总结

  • 在装饰者模式中,装饰者与被装饰者具有一致性。装饰者类是表示被装饰者的类的子类,这就体现了它们之间的一致性,它们具有相同的接口,这样,就算被装饰者被装饰了,接口还是向外暴露的(接口的透明性)
  • 可以在不改变被装饰者的前提下增加功能,如案例中在显示字符串之前对字符串进行修饰
  • 只需要一些装饰物即可添加许多功能:通过自由组合调料,可以让咖啡拥有各种不同的味道
  • 装饰者模式也有缺点:会导致程序中增加许多功能类似的很小的类

什么是父类和子类的一致性

可以将子类的实例保存到父类的变量中,也可以直接调用从父类中继承的方法

如何让自己和被委托对象有一致性

使用委托让接口具有透明性时,自己和被委托对象具有一致性

Rose和Violet都有相同的method方法。Rose将method方法的处理委托给了 Violet。这两个类虽然都有 method 方法,但是没有明确在代码中体现出“共通性”。

如果要明确地表示method方法是共通的,只需要像下面这样编写一个抽象类Flower,然后让Rose和Violet都继承并实现方法即可。

或者让Flower作为接口

文章说明

  • 本文章为本人学习尚硅谷的学习笔记,文章中大部分内容来源于尚硅谷视频(点击学习尚硅谷相关课程),也有部分内容来自于自己的思考,发布文章是想帮助其他学习的人更方便地整理自己的笔记或者直接通过文章学习相关知识,如有侵权请联系删除,最后对尚硅谷的优质课程表示感谢。
  • 本人还同步阅读《图解设计模式》书籍(图解设计模式/(日)结城浩著;杨文轩译–北京:人民邮电出版社,2017.1),进而综合两者的内容,让知识点更加全面
目录
相关文章
|
6天前
|
安全 Java 开发者
【JAVA】封装多线程原理
Java 中的多线程封装旨在简化使用、提高安全性和增强可维护性。通过抽象和隐藏底层细节,提供简洁接口。常见封装方式包括基于 Runnable 和 Callable 接口的任务封装,以及线程池的封装。Runnable 适用于无返回值任务,Callable 支持有返回值任务。线程池(如 ExecutorService)则用于管理和复用线程,减少性能开销。示例代码展示了如何实现这些封装,使多线程编程更加高效和安全。
|
6天前
|
存储 算法 Java
【JAVA】生成accessToken原理
在Java中,生成accessToken用于身份验证和授权,确保合法用户访问受保护资源。流程包括:1. 身份验证(如用户名密码、OAuth 2.0);2. 生成唯一且安全的令牌;3. 设置令牌有效期并存储;4. 客户端传递令牌,服务器验证其有效性。常见场景为OAuth 2.0协议,涉及客户端注册、用户授权、获取授权码和换取accessToken。示例代码展示了使用Apache HttpClient库模拟OAuth 2.0获取accessToken的过程。
|
2月前
|
监控 Java API
探索Java NIO:究竟在哪些领域能大显身手?揭秘原理、应用场景与官方示例代码
Java NIO(New IO)自Java SE 1.4引入,提供比传统IO更高效、灵活的操作,支持非阻塞IO和选择器特性,适用于高并发、高吞吐量场景。NIO的核心概念包括通道(Channel)、缓冲区(Buffer)和选择器(Selector),能实现多路复用和异步操作。其应用场景涵盖网络通信、文件操作、进程间通信及数据库操作等。NIO的优势在于提高并发性和性能,简化编程;但学习成本较高,且与传统IO存在不兼容性。尽管如此,NIO在构建高性能框架如Netty、Mina和Jetty中仍广泛应用。
56 3
|
2月前
|
安全 算法 Java
Java CAS原理和应用场景大揭秘:你掌握了吗?
CAS(Compare and Swap)是一种乐观锁机制,通过硬件指令实现原子操作,确保多线程环境下对共享变量的安全访问。它避免了传统互斥锁的性能开销和线程阻塞问题。CAS操作包含三个步骤:获取期望值、比较当前值与期望值是否相等、若相等则更新为新值。CAS广泛应用于高并发场景,如数据库事务、分布式锁、无锁数据结构等,但需注意ABA问题。Java中常用`java.util.concurrent.atomic`包下的类支持CAS操作。
80 2
|
3月前
|
设计模式 消息中间件 搜索推荐
Java 设计模式——观察者模式:从优衣库不使用新疆棉事件看系统的动态响应
【11月更文挑战第17天】观察者模式是一种行为设计模式,定义了一对多的依赖关系,使多个观察者对象能直接监听并响应某一主题对象的状态变化。本文介绍了观察者模式的基本概念、商业系统中的应用实例,如优衣库事件中各相关方的动态响应,以及模式的优势和实际系统设计中的应用建议,包括事件驱动架构和消息队列的使用。
|
3月前
|
设计模式 Java 数据库连接
Java编程中的设计模式:单例模式的深度剖析
【10月更文挑战第41天】本文深入探讨了Java中广泛使用的单例设计模式,旨在通过简明扼要的语言和实际示例,帮助读者理解其核心原理和应用。文章将介绍单例模式的重要性、实现方式以及在实际应用中如何优雅地处理多线程问题。
56 4
|
2月前
|
设计模式 前端开发 搜索推荐
前端必须掌握的设计模式——模板模式
模板模式(Template Pattern)是一种行为型设计模式,父类定义固定流程和步骤顺序,子类通过继承并重写特定方法实现具体步骤。适用于具有固定结构或流程的场景,如组装汽车、包装礼物等。举例来说,公司年会节目征集时,蜘蛛侠定义了歌曲的四个步骤:前奏、主歌、副歌、结尾。金刚狼和绿巨人根据此模板设计各自的表演内容。通过抽象类定义通用逻辑,子类实现个性化行为,从而减少重复代码。模板模式还支持钩子方法,允许跳过某些步骤,增加灵活性。
137 11
|
3月前
|
设计模式 安全 Java
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
|
24天前
|
设计模式
「全网最细 + 实战源码案例」设计模式——模式扩展(配置工厂)
该设计通过配置文件和反射机制动态选择具体工厂,减少硬编码依赖,提升系统灵活性和扩展性。配置文件解耦、反射创建对象,新增产品族无需修改客户端代码。示例中,`CoffeeFactory`类加载配置文件并使用反射生成咖啡对象,客户端调用时只需指定名称即可获取对应产品实例。
86 40
|
5月前
|
设计模式 数据库连接 PHP
PHP中的设计模式:提升代码的可维护性与扩展性在软件开发过程中,设计模式是开发者们经常用到的工具之一。它们提供了经过验证的解决方案,可以帮助我们解决常见的软件设计问题。本文将介绍PHP中常用的设计模式,以及如何利用这些模式来提高代码的可维护性和扩展性。我们将从基础的设计模式入手,逐步深入到更复杂的应用场景。通过实际案例分析,读者可以更好地理解如何在PHP开发中应用这些设计模式,从而写出更加高效、灵活和易于维护的代码。
本文探讨了PHP中常用的设计模式及其在实际项目中的应用。内容涵盖设计模式的基本概念、分类和具体使用场景,重点介绍了单例模式、工厂模式和观察者模式等常见模式。通过具体的代码示例,展示了如何在PHP项目中有效利用设计模式来提升代码的可维护性和扩展性。文章还讨论了设计模式的选择原则和注意事项,帮助开发者在不同情境下做出最佳决策。

热门文章

最新文章