《图解设计模式》笔记(五)一致性

十一、Composite模式:容器与内容的一致性

像文件夹与文件一样,文件夹中可以放子文件夹与文件,再比如容器中可以放更小的容器和具体内容。

Composite模式:使容器与内容具有一致性,创造出递归结构。

Composite:混合物、复合物

示例程序类图

请添加图片描述

Entry

public abstract class Entry {public abstract String getName();                               // 获取名字public abstract int getSize();                                  // 获取大小public Entry add(Entry entry) throws FileTreatmentException {   // 加入目录条目throw new FileTreatmentException();}public void printList() {                                       // 为一览加上前缀并显示目录条目一览printList("");}protected abstract void printList(String prefix);               // 为一览加上前缀// 定义实例的标准的文字显示方式public String toString() {                                      // 显示代表类的文字// 本例是将文件名和文件大小一起显示出来。以供 toString调用(即 三、Template Method模式:将具体处理交给子类)。return getName() + " (" + getSize() + ")";}
}

File

public class File extends Entry {private String name;private int size;// File类的构造函数,会根据传入的文件名和文件大小生成文件实例public File(String name, int size) {this.name = name;this.size = size;}public String getName() {return name;}public int getSize() {return size;}// 具体的显示方式是用"/”分隔prefix 和表示实例自身的文字。protected void printList(String prefix) {// 这里我们使用了表达式"/"+ this。像这样用字符串加上对象时,程序会自动地调用对象的toString方法。这是Java 语言的特点。System.out.println(prefix + "/" + this);// 上面一行与下面两种写法是等价的:
//        System.out.println(prefix + "/" + this.toString());
//        System.out.println(prefix + "/" + toString());}
}

Directory

import java.util.Iterator;
import java.util.ArrayList;public class Directory extends Entry {private String name;                    // 文件夹的名字private ArrayList directory = new ArrayList();      // 文件夹中目录条目的集合public Directory(String name) {         // 构造函数this.name = name;}public String getName() {               // 获取名字return name;}// 进行计算处理:遍历directory字段中的所有元素,计算出它们的大小的总和public int getSize() {                  // 获取大小int size = 0;Iterator it = directory.iterator();while (it.hasNext()) {Entry entry = (Entry)it.next();// 在变量size 中加上了entry的大小,entry的实例不一定是File类还是Directory类。// 但无论哪个,都可通过getSize方法得到它的大小。这就是Composite模式的特征“容器与内容的一致性”的表现。// getSize方法的递归调用与 Composite模式 的结构是相对应的。size += entry.getSize();}return size;}public Entry add(Entry entry) {         // 增加目录条目directory.add(entry);return this;}// 显示文件夹的目录条目一览。// printList方法也会递归调用,这一点和getSize方法一样。// 而且,printList方法也没有判断变量entry究竟是File类的实例还是Directory类的实例,这一点也与getSize方法一样。// 这是因为容器和内容具有一致性。protected void printList(String prefix) {       // 显示目录条目一览System.out.println(prefix + "/" + this);Iterator it = directory.iterator();while (it.hasNext()) {Entry entry = (Entry)it.next();entry.printList(prefix + "/" + name);}}
}

FileTreatmentException

// 自定义异常类:对文件调用add方法时抛出的异常
public class FileTreatmentException extends RuntimeException {public FileTreatmentException() {}public FileTreatmentException(String msg) {super(msg);}
}

Main

public class Main {public static void main(String[] args) {try {System.out.println("Making root entries...");Directory rootdir = new Directory("root");Directory bindir = new Directory("bin");Directory tmpdir = new Directory("tmp");Directory usrdir = new Directory("usr");rootdir.add(bindir);rootdir.add(tmpdir);rootdir.add(usrdir);bindir.add(new File("vi", 10000));bindir.add(new File("latex", 20000));rootdir.printList();System.out.println("");System.out.println("Making user entries...");Directory yuki = new Directory("yuki");Directory hanako = new Directory("hanako");Directory tomura = new Directory("tomura");usrdir.add(yuki);usrdir.add(hanako);usrdir.add(tomura);yuki.add(new File("diary.html", 100));yuki.add(new File("Composite.java", 200));hanako.add(new File("memo.tex", 300));tomura.add(new File("game.doc", 400));tomura.add(new File("junk.mail", 500));rootdir.printList();} catch (FileTreatmentException e) {e.printStackTrace();}}
}

角色

在这里插入图片描述

可以将 Composite角色 与它内部的 Component角色(即 Leaf角色或Composite角色)看成是父亲与孩子们的关系。

getChild方法的作用是从Component角色获取这些“孩子们”。

  • Leaf(树叶)

    表示“内容”。在该角色中不能放入其他对象。

    示例中是File类。

  • Composite(复合物)

    表示容器。可以在其中放入 Leaf角色和Composite角色。

    示例中是Directory类。

  • Component

    使 Leaf角色和Composite角色具有一致性的角色。Composite角色是Leaf角色和Composite角色的父类。

    示例中是Entry类。

  • Client

    使用 Composite模式的角色。

    示例中是Main类。

扩展思路的要点

Add方法应该放在哪里

示例中,Entry类中定义了 add方法,所做的处理是抛出异常,是因为能使用 add方法的只能是Directory类。

下面我们学习一下各种add方法的定义位置和实现方法。

  • 方法1:定义在Entry类中,报错

    这是示例程序中的做法。

    能使用 add方法的只有Directory类,它会重写 add方法,根据需求实现其处理。
    File类会继承Entry类的add方法,虽然也可以调用它的add方法,不过会抛出异常。

  • 方法2:定义在Entry类中,但什么都不做

    将add方法定义在Entry类中,但不做任何处理。

  • 方法3:声明在Entry类中,但不实现

    在Entry类中声明add 抽象方法。

    若子类需要add方法就根据需求实现该方法,否则可以简单地报错。

    优点是所有子类必须都实现 add方法,不需要add方法时的处理也可以交给子类自己去做决定。

    但会导致在File中也必须定义本来完全不需要的add(有时还包括remove 和 getChild)方法。

  • 方法4:只定义在Directory类中

    因为只有Directory类可以使用 add方法,所以可以不在Entry类中定义add方法,而只将其定义在Directory类中。

    但,如果要向Entry类型的变量(实际保存的是Directory类的实例)中add时,需先将它们一个个地类型转换(cast)为Directory类型。

到处都存在递归结构

示例中,是文件夹的结构为例,但实际上在程序世界中,到处都存在递归结构和Composite模式。

例如,在视窗系统中,一个窗口可以含有一个子窗口,这就是Composite模式的典型应用。

在文章的列表中,各列表之间可以相互嵌套,这也是一种递归结构。

将多条计算机命令合并为一条宏命令时,若使用递归结构实现宏命令,那么还可以编写出宏命令的宏命令。

通常来说,树结构的数据结构都适用Composite模式。

相关的设计模式

  • Command 模式(第22章)
    使用 Command 模式编写宏命令时使用了Composite模式。

  • Visitor模式(第13章)
    可以使用 Visitor模式访问 Composite模式中的递归结构。

  • Decorator模式(第12章)
    Composite模式通过Component角色使容器(Composite角色)和内容(Leaf角色)具有一致性。
    Decorator模式使装饰框和内容具有一致性。

十二、Decorator 模式:装饰边框与被装饰物的一致性

Decorator模式:不断地为对象添加装饰的设计模式。

Decorator指的是“装饰物”。

本章中的示例程序的功能是给文字添加装饰边框。这里所谓的装饰边框是指用“-”、“+”、“|”等字符组成的边框。

示例程序类图

在这里插入图片描述

补充说明:

Border类 装饰边框的抽象类
display字段 Display类型 表示被装饰物。
通过继承,装饰边框与被装饰物具有了相同的方法。
具体而言,Border类继承了父类的各方法。
从接口(API)角度而言,装饰边框(Border)与被装饰物(Display)具有相同的方法也就意味着它们具有一致性。

Decorator模式的结构:display字段所表示的被装饰物并仅不限于StringDisplay的实例。因为,Border也是Display类的子类,display字段所表示的也可能是其他的装饰边框(Border类的子类的实例),而且那个边框中也有一个display字段。

Display

public abstract class Display {public abstract int getColumns();               // 获取横向字符数public abstract int getRows();                  // 获取纵向行数public abstract String getRowText(int row);     // 获取第row行的字符串public void show() {                            // 全部显示// show方法使用了getRows 和getRowText等抽象方法,这属于Tempate Method模式(第3章)。for (int i = 0; i < getRows(); i++) {System.out.println(getRowText(i));}}
}

StringDisplay

public class StringDisplay extends Display {private String string;                          // 要显示的字符串public StringDisplay(String string) {           // 通过参数传入要显示的字符串this.string = string;}public int getColumns() {                       // 字符数return string.getBytes().length;}public int getRows() {                          // 行数是1return 1;}public String getRowText(int row) {             // 仅当row为0时返回值if (row == 0) {return string;} else {return null;}}
}

Border

public abstract class Border extends Display {protected Display display;          // 表示被装饰物protected Border(Display display) { // 在生成实例时通过参数指定被装饰物this.display = display;}
}

SideBorder

public class SideBorder extends Border {private char borderChar;                        // 表示装饰边框的字符public SideBorder(Display display, char ch) {   // 通过构造函数指定Display和装饰边框字符 super(display);this.borderChar = ch;}public int getColumns() {                       // 字符数为字符串字符数加上两侧边框字符数 return 1 + display.getColumns() + 1;}public int getRows() {                          // 行数即被装饰物的行数return display.getRows();}public String getRowText(int row) {             // 指定的那一行的字符串为被装饰物的字符串加上两侧的边框的字符 return borderChar + display.getRowText(row) + borderChar;}
}

FullBorder

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;}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) + "|";}}private String makeLine(char ch, int count) {         // 生成一个重复count次字符ch的字符串 StringBuffer buf = new StringBuffer();for (int i = 0; i < count; i++) {buf.append(ch);}return buf.toString();}
}

Main

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();}
}

角色

在这里插入图片描述

  • Component
    增加功能时的核心角色。
    Component角色只是定义了接口(API)。示例中是Display类。

  • ConcreteComponent
    具体实现了Component角色所定义的接口(API)。示例中是StringDisplay类。

  • Decorator(装饰物)
    该角色具有与Component角色相同的接口(API)。在它内部保存了被装饰对象——Component角色。Decorator角色知道自己要装饰的对象。示例中是Border类。

  • ConcreteDecorator(具体的装饰物)
    该角色是具体的Decorator角色。示例中是SideBorder类和FullBorder类。

拓展思路的要点

  • 接口(API)的透明性

    在 Decorator模式中,装饰边框与被装饰物具有一致性。
    表示装饰边框的Border类是表示被装饰物的Display类的子类,这就体现了它们之间的一致性。
    即,Border类(以及它的子类)与表示被装饰物的Display类具有相同的接口(API)。
    这样,即使被装饰物被边框装饰起来了,接口(API)也不会被隐藏起来。其他类依然可以调用getColumns、getRows、,getRowText以及show方法。这就是接口(API)的“透明性”。

    在示例程序中,实例b4被装饰了多次,但是接口(API)却没有发生任何变化。
    得益于接口(API)的透明性,Decorator模式中也形成了类似于Composite模式中的递归结构。
    即,装饰边框里面的“被装饰物”实际上又是别的物体的“装饰边框”。
    就像是剥洋葱时以为洋葱心要出来了,结果却发现还是皮。
    不过,Decorator模式虽然与Composite模式一样,都具有递归结构,但是它们的使用目的不同。

  • Decorator模式的主要目的是通过添加装饰物来增加对象的功能。

    在不改变被装饰物的前提下增加功能
    在 Decorator模式中,装饰边框与被装饰物具有相同的接口(API)。
    虽然接口(API)是相同的,但越装饰,功能越多。
    例如,用SideBorder装饰Display后,就可以在字符串的左右两侧加上装饰字符。
    若再用 FullBorder装饰,则可以在字符串的四周加上边框。
    此时,我们完全不需要对被装饰的类做任何修改。这样,我们就实现了不修改被装饰的类即可增加功能。

    Decorator模式使用了委托。对“装饰边框”提出的要求(调用装饰边框的方法)会被转交(委托)给“被装饰物”去处理。
    以示例程序来说,就是SideBorder类的getColumns方法调用了display,getColumns ()。
    除此以外,getRows方法也调用了display.getRows()

  • 可以动态地增加功能

    Decorator 模式中用到了委托,它使类之间形成了弱关联关系。

    因此,不用改变框架代码,就可以生成一个与其他对象具有不同关系的新对象。

  • 只需要一些装饰物即可添加许多功能

    使用 Decorator模式可以为程序添加许多功能。只要准备一些装饰边框(ConcreteDecorator 角色),即使这些装饰边框都只有简单功能,也可将它们自由组合成为新的对象。
    这就像自由选择各种口味的冰激凌一样。冰激凌店不必准备所有的冰激凌成品,而是准备各种香料,顾客下单后在冰激凌上加各种香料就可以了。
    Decorator模式就是可以应对这种多功能对象的需求的一种模式。

  • java.io包与Decorator模式

    java.io包是用于输入输出(Input/Output,简称I/O)的包。这里,我们使用了 Decorator 模式。
    首先,可以用Reader reader = new FileReader("datafile.txt");生成一个读取文件的实例。
    然后,也可以用Reader reader = new BufferedReader(new Fi1eReader("datafile.txt"));在读取文件时将文件内容放入缓冲区。
    这样,在生成BufferedReader类的实例时,会指定将文件读取到FileReader类的实例中。
    再然后,也可以这样管理行号:Reader reader = new LineNumberReader(New BufferedReader(New FileReader("datafile.txt")))

    无论是LineNumberReader类的构造函数还是BufferedReader类的构造函数,都可以接收Reader类(的子类)的实例作为参数,因此我们可以像上面那样自由地进行各种组合。
    还可以只管理行号,但不进行缓存处理:Reader reader = new LineNumberReader (new FileReader ("datafile.txt"));
    接下来,我们还会管理行号,进行缓存,但是我们不从文件中读取数据,而是从网络中读取数据(下面的代码中省略了细节部分和异常处理)。

    java.net.Socket socket = new Socket (hostname, portnumber):
    Reader reader = new LineNumberReader (new BufferedReader (new InputstreamReader(socket.getInputstream())));
    

    这里使用的InputStreamReader类既接收getInputStream()方法返回的InputStream类的实例作为构造函数的参数,也提供了Reader类的接口(API)(这属于第2章学习过的Adapter模式)。
    除了java.io包以外,我们还在javax.swing.border包中使用了Decorator模式。
    javax.swing.border包为我们提供了可以为界面中的控件添加装饰边框的类。

  • 导致增加许多很小的类

    Decorator 模式的一个缺点是会导致程序中增加许多功能类似的很小的类。

相关的设计模式

  • Adapter模式(第2章)

    Decorator模式可以在不改变被装饰物的接口(API)的前提下,为被装饰物添加边框(透明性)。

    Adapter模式用于适配两个不同的接口(API)。

  • Stragety模式(第10章)

    Decorator模式可以像改变被装饰物的边框或是为被装饰物添加多重边框那样,来增加类的功能。

    Stragety 模式通过整体地替换算法来改变类的功能。

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

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

相关文章

爬虫学习笔记之Robots协议相关整理

定义 Robots协议也称作爬虫协议、机器人协议&#xff0c;全名为网络爬虫排除标准&#xff0c;用来告诉爬虫和搜索引擎哪些页面可以爬取、哪些不可以。它通常是一个叫做robots.txt的文本文件&#xff0c;一般放在网站的根目录下。 robots.txt文件的样例 对有所爬虫均生效&#…

电脑可以自己换显卡吗?怎么操作

电脑是否可以自己换显卡主要取决于电脑的类型&#xff08;台式机或笔记本&#xff09;以及电脑的硬件配置。以下是对这一问题的详细解答及操作步骤&#xff1a; 一、判断电脑是否支持更换显卡 台式机&#xff1a;大多数台式电脑都支持更换显卡。只要主板上有PCIe插槽&#xff…

【玩转 Postman 接口测试与开发2_014】第11章:测试现成的 API 接口(下)——自动化接口测试脚本实战演练 + 测试集合共享

《API Testing and Development with Postman》最新第二版封面 文章目录 3 接口自动化测试实战3.1 测试环境的改造3.2 对列表查询接口的测试3.3 对查询单个实例的测试3.4 对新增接口的测试3.5 对修改接口的测试3.6 对删除接口的测试 4 测试集合的共享操作4.1 分享 Postman 集合…

华为支付-免密支付接入免密代扣说明

免密代扣包括支付并签约以及签约代扣场景。 开发者接入免密支付前需先申请开通签约代扣产品&#xff08;即申请配置免密代扣模板及协议模板ID&#xff09;。 华为支付以模板维度管理每一个代扣扣费服务&#xff0c;主要组成要素如下&#xff1a; 接入免密支付需注意&#x…

Redis - 全局ID生成器 RedisIdWorker

文章目录 Redis - 全局ID生成器 RedisIdWorker一、引言二、实现原理三、代码实现代码说明 四、使用示例示例说明 五、总结 Redis - 全局ID生成器 RedisIdWorker 一、引言 在分布式系统中&#xff0c;生成全局唯一ID是一个常见的需求。传统的自增ID生成方式在分布式环境下容易出…

YOLOv11实时目标检测 | 摄像头视频图片文件检测

在上篇文章中YOLO11环境部署 || 从检测到训练https://blog.csdn.net/2301_79442295/article/details/145414103#comments_36164492&#xff0c;我们详细探讨了YOLO11的部署以及推理训练&#xff0c;但是评论区的观众老爷就说了&#xff1a;“博主博主&#xff0c;你这个只能推理…

用Python获取股票数据并实现未来收盘价的预测

获取数据 先用下面这段代码获取上证指数的历史数据&#xff0c;得到的csv文件数据&#xff0c;为后面训练模型用的 import akshare as ak import pandas as pd# 获取上证指数历史数据 df ak.stock_zh_index_daily(symbol"sh000001")# 将数据保存到本地CSV文件 df.…

RK3576——USB3.2 OTG无法识别到USB设备

问题&#xff1a;使用硬盘接入到OTG接口无热插拔信息&#xff0c;接入DP显示屏无法正常识别到显示设备&#xff0c;但是能通过RKDdevTool工具烧录系统。 问题分析&#xff1a;由于热插拔功能实现是靠HUSB311芯片完成的&#xff0c;因此需要先确保HUSB311芯片驱动正常工作。 1. …

RabbitMQ深度探索:前置知识

消息中间件&#xff1a; 消息中间件基于队列模式实现异步 / 同步传输数据作用&#xff1a;可以实现支撑高并发、异步解耦、流量削峰、降低耦合 传统的 HTTP 请求存在的缺点&#xff1a; HTTP 请求基于响应的模型&#xff0c;在高并发的情况下&#xff0c;客户端发送大量的请求…

maven如何不把依赖的jar打包到同一个jar?

spring boot项目打jar包部署&#xff1a; 经过以下步骤&#xff0c; 最终会形成maven依赖的多个jar&#xff08;包括lib下添加的&#xff09;、 我们编写的程序代码打成一个jar&#xff0c;将程序jar与 依赖jar分开&#xff0c;便于管理&#xff1a; success&#xff1a; 最终…

网络工程师 (21)网络的性能

一、速率&#xff08;数据率或比特率&#xff09; 定义&#xff1a;数据在数字信道上传送的速率&#xff0c;通常以比特每秒&#xff08;bps&#xff09;为单位。常见的速率单位还有千比特每秒&#xff08;kbit/s&#xff09;、兆比特每秒&#xff08;Mbit/s&#xff09;和吉比…

UE5 蓝图学习计划 - Day 14:搭建基础游戏场景

在上一节中&#xff0c;我们 确定了游戏类型&#xff0c;并完成了 项目搭建、角色蓝图的基础设置&#xff08;移动&#xff09;。今天&#xff0c;我们将进一步完善 游戏场景&#xff0c;搭建 地形、墙壁、机关、触发器 等基础元素&#xff0c;并添加角色跳跃功能&#xff0c;为…

计算机毕业设计hadoop+spark+hive民宿推荐系统 酒店推荐系统 民宿价格预测 酒店价预测 机器学习 深度学习 Python爬虫 HDFS集群

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

金蝶云星空k3cloud webapi报“java.lang.Class cannot be cast to java.lang.String”的错误

最近在对接金蝶云星空k3cloud webapi时&#xff0c;报一个莫名其妙的转换异常&#xff0c;具体如下&#xff1a; 同步部门异常! ERP接口登录异常&#xff1a;java.lang.Class cannot be cast to java.lang.String at com.jkwms.k3cloudSyn.service.basics.DeptK3CloudService.…

html的字符实体和颜色表示

在HTML中&#xff0c;颜色可以通过以下几种方式表示&#xff0c;以下是具体的示例&#xff1a; 1. 十六进制颜色代码 十六进制颜色代码以#开头&#xff0c;后面跟随6个字符&#xff0c;每两个字符分别表示红色、绿色和蓝色的强度。例如&#xff1a; • #FF0000&#xff1a;纯红…

老游戏回顾:G2

一个老的RPG游戏。 剧情有独到之处。 ------- 遥远的过去&#xff0c;古拉纳斯将希望之光给予人们&#xff0c;人类令希望之光不断扩大&#xff0c;将繁荣握在手中。 但是&#xff0c;暗之恶魔巴鲁玛将光从人类身上夺走。古拉纳斯为了守护人类与其展开了一场激战&#xff0c…

E4982A,keysight是德科技台式LCR表

是德科技keysightE4982A台式LCR表 是德KEYSIGHT的精密型LCR表E4982A&#xff0c;针对SMD电感器、EMI滤波器等无源元器件的制造测试展现出卓越性能&#xff0c;特别适用于1 MHz至3 GHz高频率范围内的阻抗测试。此外&#xff0c;E4982A还广泛应用于研发领域&#xff0c;凭借其强…

C++, STL容器 array:固定大小数组深度解析

文章目录 引言一、设计哲学与底层实现1.1 零抽象成本的封装1.2 性能特征二、内存优化实践2.1 缓存友好性对比2.2 内存碎片防护三、高级内存管理技巧3.1 精准内存对齐3.2 内存复用模式四、工程实践指南4.1 适用场景4.2 陷阱规避五、未来演进结语引言 在C++标准库中,std::array…

013-51单片机红外遥控器模拟控制空调,自动制冷制热定时开关

主要功能是通过红外遥控器模拟控制空调&#xff0c;可以实现根据环境温度制冷和制热&#xff0c;能够通过遥控器设定温度&#xff0c;可以定时开关空调。 1.硬件介绍 硬件是我自己设计的一个通用的51单片机开发平台&#xff0c;可以根据需要自行焊接模块&#xff0c;这是用立创…

(苍穹外卖)项目结构

苍穹外卖项目结构 后端工程基于 maven 进行项目构建&#xff0c;并且进行分模块开发。 1). 用 IDEA 打开初始工程&#xff0c;了解项目的整体结构&#xff1a; 对工程的每个模块作用说明&#xff1a; 序号名称说明1sky-take-outmaven父工程&#xff0c;统一管理依赖版本&…