设计模式_装饰器模式_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作为接口

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

girhub添加 SSH 密钥

1 打开终端 输入 ssh-keygen -t rsa -b 4096 -C "github邮箱地址"如果不需要密码可以一路回车 出现这个页面就是生存成功了 open ~/.ssh // 打开.ssh 找到id_rsa.pub复制出内容新建ssh密钥输入内容,保存即可

jmeter下载及安装配置

前言 本文是在win10环境下安装使用jmeter&#xff0c;jmeter可以运行在多平台上Windows和Linux。 环境准备&#xff1a; java 1.8 jmeter 5.1.1 jmeter环境 jmeter环境依赖JAVA环境&#xff0c;需安装JDK1.8环境&#xff0c;JDK环境安装网上一大堆教程&#xff0c;我这里就…

Socket实现服务器和客户端

Socket 编程是一种用于在网络上进行通信的编程方法&#xff0c;以下代码可以实现在不同主机之间传输数据。 Socket 编程中服务器端和客户端的基本步骤&#xff1a;服务器端步骤&#xff1a; 1.创建 Socket&#xff1a; int serverSocket socket(AF_INET, SOCK_STREAM, 0);…

详讲api网关之kong的基本概念及安装和使用(一)

什么是api网关 前面我们聊过sentinel&#xff0c;用来限流熔断和降级&#xff0c;如果你只有一个服务&#xff0c;用sentinel自然没有问题&#xff0c;但是如果是有多个服务&#xff0c;特别是微服务的兴起&#xff0c;那么每个服务都使用sentinel就给系统维护带来麻烦。那么网…

附1:k8s服务器初始化

转载说明&#xff1a;如果您喜欢这篇文章并打算转载它&#xff0c;请私信作者取得授权。感谢您喜爱本文&#xff0c;请文明转载&#xff0c;谢谢。 关联文章&#xff1a; 《RKE快速搭建离线k8s集群并用rancher管理界面》 《附2&#xff1a;rke安装的k8s集群新增主机》 1.创建…

IDEA使用技巧总结(强迫症福音+新手必看)

前言 本文主要整理与分享个人在使用IDEA做Java开发时做的各种配置&#xff0c;算是几年来的总结。 本人使用的 idea 版本比较老了&#xff0c;2019.1 版本&#xff0c;JDK 版本也老&#xff0c;1.8 版本&#xff0c;主打的是情怀不同版本的 idea 影响不大&#xff0c;基本上在设…

【C++】list讲解及模拟

目录 list的基本介绍 list模拟实现 一.创建节点 二.迭代器 1.模版参数 2.迭代器的实现&#xff1a; a. ! b. c. -- d. *指针 e.&引用 整体iterator (与const复用)&#xff1a; 三.功能实现 1.模版参数 2.具体功能实现&#xff1a; 2.1 构造函数 2.2 begi…

【oracle】oracle客户端及oracle连接工具

一、关于oracle客户端 1.1 Oracle Client 完整客户端 包含完整的客户端连接工具。 包很大&#xff0c;需要安装 1.2 instantclient 即时客户端 是 Oracle(R) 发布的轻量级数据库客户端&#xff0c;减少甚至只包含几个文件&#xff0c;您无需安装标准的客户端&#xff0c;就可以…

Element UI样式修改之NavMenu导航菜单箭头样式修改

UI设计稿给的菜单箭头样式可能与我们饿了么组件NavMenu的菜单箭头样式不一致,目前我们侧边导航菜单的上下翻转箭头如下所示: 希望得到如下的结果: 找到饿了么Icon里我们想要向下箭头,F12后复制content内容content: “\e790”; content: "\e790";然后将默认的c…

【Delphi】程序实现Windows电脑关机、重启、注销(源代码)

目录 一、API函数说明 1. GetCurrentProcess 2. OpenProcessToken 3. LookupPrivilegeValue 4. AdjustTokenPrivileges 5. ExitWindowsEx 二、Delphi实现源代码 在日常软件开发中&#xff0c;可能会遇到通过程序自动关闭电脑&#xff0c;在早期Windows 9x下&#xff0c;…

西安石油大学C++上机实验汇总

考试题难度就像第三章第五章课后题的难度 基础知识一定要掌握&#xff01;&#xff01;&#xff01; 上机一&#xff1a;类与对象程序设计&#xff08;2 学时&#xff09; 上机目的 掌握声明类的方法。掌握类和类的成员的概念以及定义对象的方法。掌握构造函数和析构函数的…

家居图册制作方法,快来看看

​随着家居设计的流行&#xff0c;越来越多的人开始关注家居装饰和家居用品。据统计&#xff0c;家居市场每年的销售额逐年增长。而家居图册作为家居装饰的重要组成部分&#xff0c;其制作方法也备受关注。 那要怎么制作呢&#xff1f;准备好这个工具&#xff1a;FLBOOK在线制作…

百度Apollo | 实车自动驾驶:感知、决策、执行的无缝融合

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏:《linux深造日志》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! ⛳️ 推荐 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下…

风口抓猪-借助亚马逊云科技EC2服务器即刻构建PalWorld(幻兽帕鲁)私服~~~持续更新中

Pocketpair出品的生存类游戏《幻兽帕鲁》最近非常火&#xff0c;最高在线人数已逼近200万。官方服务器亚历山大&#xff0c;游戏开发商也提供了搭建私人专用服务器的方案&#xff0c;既可以保证稳定的游戏体验&#xff0c;也可以和朋友一起联机游戏&#xff0c;而且还能自定义经…

大创项目推荐 题目:基于LSTM的预测算法 - 股票预测 天气预测 房价预测

文章目录 0 简介1 基于 Keras 用 LSTM 网络做时间序列预测2 长短记忆网络3 LSTM 网络结构和原理3.1 LSTM核心思想3.2 遗忘门3.3 输入门3.4 输出门 4 基于LSTM的天气预测4.1 数据集4.2 预测示例 5 基于LSTM的股票价格预测5.1 数据集5.2 实现代码 6 lstm 预测航空旅客数目数据集预…

1.25 C++ day2

思维导图 自己封装一个矩形类(Rect)&#xff0c;拥有私有属性:宽度(width)、高度(height)&#xff0c; 定义公有成员函数: 初始化函数:void init(int w, int h) 更改宽度的函数:set_w(int w) 更改高度的函数:set_h(int h) 输出该矩形的周长和面积函数:void show() 代码&a…

vue实现甘特图

目录 实现效果 一、安装依赖 二、使用 二、绕过license 实现效果 一、安装依赖 npm i --save vue-gantt-schedule-timeline-calendar 实现甘特图需先安装上述依赖&#xff0c;安装依赖实际上是通过gantt-schedule-timeline-calendar来实现的。所以node_module中因包含以下…

SpringBlade微服务开发平台

采用前后端分离的模式&#xff0c;前端开源两个框架&#xff1a;Sword (基于 React、Ant Design)、Saber (基于 Vue、Element-UI)后端采用SpringCloud全家桶&#xff0c;并同时对其基础组件做了高度的封装&#xff0c;单独开源出一个框架&#xff1a;BladeToolBladeTool已推送至…

视频占内存太大如何压缩?3种简单有效的方法~

很多时候我们需要压缩视频大小&#xff0c;比如节省存储空间、满足平台上传要求等&#xff0c;下面就给大家总结了几个简单且好用的视频压缩技巧&#xff0c;需要的朋友快来学起来~ 方法一&#xff1a;嗨格式压缩大师 嗨格式压缩大师是一款专业的压缩软件&#xff0c;可以压缩…

2024年新提出的算法:一种新的基于数学的优化算法——牛顿-拉夫森优化算法|Newton-Raphson-based optimizer,NRBO

1、简介 开发了一种新的元启发式算法——Newton-Raphson-Based优化器&#xff08;NRBO&#xff09;。NRBO受到Newton-Raphson方法的启发&#xff0c;它使用两个规则&#xff1a;Newton-Raphson搜索规则&#xff08;NRSR&#xff09;和Trap Avoidance算子&#xff08;TAO&#…