设计模式_装饰器模式_Decorator

生活案例

咖啡厅 咖啡定制案例

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

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

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

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

【方案二】

在这里插入图片描述

分析:

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

装饰者模式介绍

介绍

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

出场角色

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

在这里插入图片描述

案例实现

案例一(咖啡厅问题)

类图

在这里插入图片描述

在这里插入图片描述

代码实现

【被装饰主体】

package com.test.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.test.decorator;public class Coffee  extends Drink {@Override
public float cost() {
return super.getPrice();
}}

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

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

【单品咖啡:美式咖啡】

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

【单品咖啡:浓咖啡】

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

【装饰者】

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

【具体装饰者:牛奶】

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

【具体装饰者:豆浆】

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

【主类】

package com.test.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.test.decorator;public class DeCaf extends Coffee {public DeCaf() {
setDes(" 无因咖啡 ");
setPrice(1.0f);
}
}

【主类】

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

【具体修饰者1】

package com.test.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.test.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.test.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作为接口

在这里插入图片描述

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

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

相关文章

机器学习_常见算法比较模型效果(LR、KNN、SVM、NB、DT、RF、XGB、LGB、CAT)

文章目录 KNNSVM朴素贝叶斯决策树随机森林 KNN “近朱者赤&#xff0c;近墨者黑”可以说是 KNN 的工作原理。 整个计算过程分为三步&#xff1a; 计算待分类物体与其他物体之间的距离&#xff1b;统计距离最近的 K 个邻居&#xff1b;对于 K 个最近的邻居&#xff0c;它们属于…

Qt编写linux系统onvif工具(支持预览/云台/预置位/录像等)

一、功能特点 广播搜索设备&#xff0c;支持IPC和NVR&#xff0c;依次返回。可选择不同的网卡IP进行对应网段设备的搜索。依次获取Onvif地址、Media地址、Profile文件、Rtsp地址。可对指定的Profile获取视频流Rtsp地址&#xff0c;比如主码流地址、子码流地址。可对每个设备设…

单元测试——题目十二

目录 题目要求: 定义类 测试类 题目要求: 根据下列流程图编写程序实现相应处理,执行j=10*x-y返回文字“j1=:”和计算值,执行j=(x-y)*(10⁵%7)返回文字“j2=:”和计算值,执行j=y*log(x+10)返回文字“j3=:”和计算值。 编写程序代码,使用JUnit框架编写测试类对编写的…

idea中使用带provide修饰的依赖,导致ClassNotFound

1、provide修饰的依赖作用&#xff1a; 编译时起作用&#xff0c;而运行及打包时不起作用。程序打包到Linux上运行时&#xff0c;若Linux上也有这些依赖&#xff0c;为了在Linux上运行时避免依赖冲突&#xff0c;可以使用provide修饰&#xff0c;使依赖不打包进入jar中 2、可能…

Map集合(二)

HashMap HashMap集合的底层原理 HashMap跟HashSet的底层原理是一模一样的&#xff0c;都是基于哈希表实现的。 实际上&#xff1a;原来学的Set集合的底层就是基于Map实现的&#xff0c;只是Set集合中的元素只要键数据&#xff0c;不要值数据而已。 哈希表 哈希表是一种增删…

Python Flask与APScheduler构建简易任务监控

1. Flask Web Flask诞生于2010年&#xff0c;是用Python语言&#xff0c;基于Werkzeug工具箱编写的轻量级、灵活的Web开发框架&#xff0c;非常适合初学者或小型到中型的 Web 项目。 Flask本身相当于一个内核&#xff0c;其他几乎所有的功能都要用到扩展&#xff08;邮件扩展…

代码随想录算法训练营31期day4,力扣24+19+02.07+142

24&#xff0c;动指针 class Solution { public:ListNode* swapPairs(ListNode* head) {//建立虚拟头结点auto dummynew ListNode(-1);dummy->nexthead;for(auto pdummy;p->next&&p->next->next;){auto ap->next;auto ba->next;p->nextb;a->n…

tee漏洞学习-翻译-1:从任何上下文中获取 TrustZone 内核中的任意代码执行

原文&#xff1a;http://bits-please.blogspot.com/2015/03/getting-arbitrary-code-execution-in.html 目标是什么&#xff1f; 这将是一系列博客文章&#xff0c;详细介绍我发现的一系列漏洞&#xff0c;这些漏洞将使我们能够将任何用户的权限提升到所有用户的最高权限 - 在…

POLYGON Military - Low Poly 3D Art by Synty

这是一个非常全面的资产包,可满足您的所有军事需求。一个绝对庞大的低多边形资产包,用于构建您的梦想游戏! 模块化部分易于以各种组合方式拼接在一起。 此包中包含 1500 多个详细的预制件。 主要特征 - 完全模块化武器系统 - 超级可定制的角色 -沙漠主题建筑和环境 - 建筑物…

day16打卡

day16打卡 104. 二叉树的最大深度 递归法时间复杂度&#xff1a;O(N)&#xff0c;空间复杂度&#xff1a;O(N) class Solution { public:int maxDepth(TreeNode* root) {if(root nullptr) return 0;return 1 max(maxDepth(root->left), maxDepth(root->right));} };…

springboot-mybatis项目

一、后端开发环境搭建 1、File->New->Projet 2选择 Spring Initializr &#xff0c;然后选择默认的 url 点击next 3勾选Spring Web、SQL模板&#xff0c;next 4点击finish&#xff0c;搭建完成 二 数据库 1 新建数据库 2 执行sql建表 SET NAMES utf8mb4; SET FOREIGN…

C语言练习题110例(十)

91.杨辉三角 题目描述: KK知道什么叫杨辉三角之后对杨辉三角产生了浓厚的兴趣&#xff0c;他想知道杨辉三角的前n行&#xff0c;请编程帮他 解答。杨辉三角&#xff0c;本质上是二项式(ab)的n次方展开后各项的系数排成的三角形。其性质包括&#xff1a;每行的端点数为1&…

安利一款抢票软件堪称“业界良心”,全网好评!

马上就到了春运了&#xff0c;有不少网友反映12306买票太难了。 有粉丝在后台留言问有没有抢票软件&#xff1f; 知名公司开发的抢票软件&#xff0c;需要助力、需要用钱买加速包&#xff0c;这对于需要白嫖的朋友来说无疑是“雪上加霜”&#xff01; 这里从解决实际问题的角度…

[漏洞复现]Redis 沙盒逃逸漏洞(CVE-2022-0543)

一、漏洞情况分析 Redis 存在代码注入漏洞&#xff0c;攻击者可利用该漏洞远程执行代码。 二、漏洞复现 春秋云境.com 进入靶场 开始复现 三、漏洞处置建议 把靶场关了&#xff0c;跟漏洞说“白白吧

星环科技基于第五代英特尔®至强®可扩展处理器的分布式向量数据库解决方案重磅发布

12月15日&#xff0c;2023 英特尔新品发布会暨 AI 技术创新派对上&#xff0c;星环科技基于第五代英特尔至强可扩展处理器的Transwarp Hippo分布式向量数据库解决方案重磅发布。该方案利用第五代英特尔至强可扩展处理器带来的强大算力&#xff0c;实现了约 2 倍的代际性能提升&…

如何在外远程访问家中本地威联通QNAP NAS

文章目录 前言1. 威联通安装cpolar内网穿透2. 内网穿透2.1 创建隧道2.2 测试公网远程访问 3. 配置固定二级子域名3.1 保留二级子域名3.2 配置二级子域名 4. 使用固定二级子域名远程访问 前言 购入威联通NAS后&#xff0c;很多用户对于如何在外在公网环境下的远程访问威联通NAS…

【Python爬虫入门到精通】小白也能看懂的知识要点与学习路线

文章目录 1. 写在前面2. 爬虫行业情况3. 学习路线 【作者主页】&#xff1a;吴秋霖 【作者介绍】&#xff1a;Python领域优质创作者、阿里云博客专家、华为云享专家。长期致力于Python与爬虫领域研究与开发工作&#xff01; 【作者推荐】&#xff1a;对JS逆向感兴趣的朋友可以关…

记一次 .NET某工控自动化系统 崩溃分析

一&#xff1a;背景 1. 讲故事 前些天微信上有位朋友找到我&#xff0c;说他的程序偶发崩溃&#xff0c;分析了个把星期也没找到问题&#xff0c;耗费了不少人力物力&#xff0c;让我能不能帮他看一下&#xff0c;给我申请了经费&#xff0c;哈哈&#xff0c;遇到这样的朋友就…

Python之数据可视化基础

目录 一 JSON数据格式转换 二 pyecharts模块 三 Pyecharts入门 四 数据可视化之疫情折线图 一 JSON数据格式转换 什么是JSON? JSON&#xff08;JavaScript Object Notation&#xff09;是一种轻量级的数据交换格式。它以易于阅读和编写的方式来表示结构化数据。JSO…

机器学习的精髓-梯度下降算法

目 1. 梯度下降算法2. 梯度下降求解3. 总结 1. 梯度下降算法 梯度下降算法是一种优化算法&#xff0c;用于最小化函数的数值方法。它通过沿着函数梯度的反方向来更新参数&#xff0c;以逐步减小函数值。这一过程重复进行直到达到收敛条件。梯度下降算法有多种变体&#xff0c;…