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

news2024/11/14 20:46:44

生活案例

咖啡厅 咖啡定制案例

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

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

【方案一】
在这里插入图片描述

写一个抽象类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),进而综合两者的内容,让知识点更加全面

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/798667.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SAP安装笔记

1、准备安装介质&#xff0c;SWPM10SP25&#xff0c;51050829_NW750_JavaExport、SAP_HANA_CLIENT、kernel放到/sapcd/NetWeaver目录下 ​​​​​​​ 进入SWPM10SP25执行./sapinst安装 2、待出现 “Open your browser and paste the following URL address to access the G…

如何从0开始搭建公司自动化测试框架?25k入职新公司的我是这样做的..

搭建的自动化测试框架要包括API测试&#xff0c;UI测试&#xff0c;APP测试三类。以上三类其实可以简化为两类&#xff0c;那就是&#xff1a; 1&#xff09;接口自动化测试框架搭建 2&#xff09;UI自动化测试框架搭建。 没问题&#xff0c;安排&#xff0c;且是手把手教你如何…

高校宿舍报修管理系统的设计与实现(论文+源码)_kaic

摘 要 随着科技的发展&#xff0c;信息化的管理手段早以在人们生活的各个方面取代了传统的管理手段&#xff0c;以先进管理理念为基础的现代化信息管理系统已经成为了许多机构的必备工具。在如今大学的校园里&#xff0c;有着许许多多的信息化管理系统&#xff0c;如图书管理系…

尚医通06:数据字典+EasyExcel+mongodb

内容介绍 1、数据字典列表前端 2、EasyExcel介绍、实例 3、数据字典导出接口、前端 4、数据字典导入接口、前端 5、数据字典添加redis缓存 6、MongoDB简介 7、MongoDB安装 8、MongoDB基本概念 数据字典列表前端 1、测试问题 &#xff08;1&#xff09;报错日志 &am…

LabVIEW开发小型减阻试验平台

LabVIEW开发小型减阻试验平台 湍流摩擦在粘性流体的阻力中起着重要作用&#xff0c;减少湍流摩擦是流体力学领域的热门话题之一。在油气管道的长距离流体输送中&#xff0c;泵站提供的几乎所有动力都用于克服流体的胫骨摩擦。在流体输送领域&#xff0c;船舶的蒙皮摩擦阻力占总…

函数详细解析

目录 形参有默认值的函数 基本概述 指令角度 设定形参默认值 内联函数 基本概述 代码段 函数重载 答疑解惑 形参有默认值的函数 基本概述 从右向左设定形参默认值 形参默认值可以在定义时设定&#xff0c;也可以在声明时设定 每个形参的默认值只能被设定一次 指令角度…

华为HCIP第二节-------------------------ISIS

IS-IS&#xff08;Intermediate System to Intermediate System&#xff0c;中间系统到中间系统&#xff09;是ISO &#xff08;International Organization for Standardization&#xff0c;国际标准化组织&#xff09;为它的CLNP&#xff08;ConnectionLessNetwork Protocol&…

1200*B. Vanya and Lanterns

Examples input 7 15 15 5 3 7 9 14 0 output 2.5000000000 input 2 5 2 5 output 2.0000000000 解析&#xff1a; 最大距离即为每相邻两盏灯之间的最大距离/2 注意起点没有灯&#xff0c;终点可能有灯&#xff0c;需要分别判断 #include<bits/stdc.h> using nam…

前端html中让两个或者多个div在一行显示,用style给div加上css样式

文章目录 前言一、怎么让多个div在一行显示 前言 DIV是层叠样式表中的定位技术&#xff0c;全称DIVision&#xff0c;即为划分。有时可以称其为图层。DIV在编程中又叫做整除&#xff0c;即只得商的整数。 DIV元素是用来为HTML&#xff08;标准通用标记语言下的一个应用&#x…

如何在MacBook上彻底删除mysql

好久以前安装过&#xff0c;但是现在配置mysql一直出错&#xff0c;索性全部删掉重新配置。 一、停止MySQL服务 首先&#xff0c;请确保 MySQL 服务器已经停止运行&#xff0c;以免影响后续的删除操作。 sudo /usr/local/mysql/support-files/mysql.server stop如果你输入之…

DAY3,Qt(完成闹钟的实现,定时器事件处理函数的使用)

1.完成闹钟的实现&#xff0c;到点播报文本框的内容&#xff1b; ---alarm.h---头文件 #ifndef ALARM_H #define ALARM_H#include <QWidget> #include <QTimerEvent> //定时器处理函数类 #include <QTime> //时间类 #include <QPushButton> //按钮…

小红书舆情处理方法丨小红书负面笔记处理的三种技巧

小红书作为一个生活分享平台&#xff0c;经常会刷到一些负面笔记。更多用户的浏览&#xff0c;点赞收藏&#xff0c;评论行为会使笔记热度更高&#xff0c;笔记搜索排名靠前&#xff0c;如果品牌对负面舆情处置方法不当很可能影响公司品牌信誉&#xff0c;导致用户流失。 小红书…

WEB:unseping

背景知识 php序列化和反序列化 命令执行绕过方式 题目 进行代码审计 可知为反序列化 整体是创建case类&#xff0c;可接受post传来的ctf值 _consturuct函数,是在函数调动前启用&#xff0c;构造了$method和$args两个变量。 _dexstruct函数在变量摧毁的时使用&#xff0c;所…

HDU - 7315 Data Generation( 2023“钉耙编程”中国大学生算法设计超级联赛第四场 D)

题目大意 Yoshinow2001 \text{Yoshinow2001} Yoshinow2001 正在为他的问题生成数据。他想要生成 { 0 , … , n − 1 } \{0,…,n−1\} {0,…,n−1} 的一个随机排列&#xff0c;因此他使用了以下算法&#xff1a; 在这里&#xff0c;我们可以假设函数 rand ⁡ ( ) m o d n \ope…

超声功率放大器基本原理和设计流程

超声功率放大器是一种将低功率信号放大到高功率信号的设备&#xff0c;是实现超声成像、治疗和检查的关键组件。它主要由功率放大电路、控制电路、保护电路等部分组成。本文将介绍超声功率放大器的技术方案&#xff0c;包括其基本原理、设计流程及注意事项。 基本原理 超声功率…

闭环排队理论简介

闭环排队理论简介 1. 系统情景2. 数学描述 在排队理论简介一文中&#xff0c;笔者详细介绍了排队理论的基本内容。在该文中&#xff0c;申请流是来自系统外部的&#xff0c;其强度&#xff08;或密度&#xff09;并不取决于系统本身&#xff0c;也不取决于系统的状态。而在本文…

网络安全法律法规

数据参考&#xff1a;CISP官方 目录 国家立法体系网络安全法解析网络安全相关法律 一、国家立法体系 1、我国的立法体系 我国的立法体系在网络空间治理中扮演着基础工作的角色。为了应对快速发展的网络技术和威胁&#xff0c;我国采取了多级立法机制来完善网络空间的法律…

【uniapp】十分钟带你封装uniapp的api请求

前端面试题库 &#xff08;面试必备&#xff09; 推荐&#xff1a;★★★★★ 地址&#xff1a;前端面试题库 前言 最近刚好接了个私活&#xff0c;顺便把之前做到的项目中没有写出来的功能点单独拿出来写一篇吧&#xff01; 其实在我的uniapp专栏里面写了不少关…

Kafka 入门到起飞系列 - 怎么从ISR中选出的Leader呢? Leader选举机制

上文我们讲了分区分成Leader 和 Follower两种角色&#xff0c;当Leader宕机后&#xff0c;会从ISR同步副本中选出一个分区作为leader分区继续工作&#xff0c;那么leader是怎么选出来的呢&#xff1f; 怎么从ISR中选出的Leader呢&#xff1f; Leader选举机制 比如有这么个分布…

梯度下降法和牛顿法

梯度下降法和牛顿法都是优化方法。 梯度下降法 梯度下降法和相关知识可以参考导数、偏导数、梯度、方向导数、梯度下降、二阶导数、二阶方向导数一文。梯度下降法是一种迭代地每次沿着与梯度相反方向前进的不断降低损失函数的优化方法。梯度下降只用到一阶导数的信息&#xf…