当前位置: 首页 > article >正文

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

十一、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() {                          // 行数是1
        return 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.kler.cn/a/535040.html

相关文章:

  • Kafka 可靠性探究—副本刨析
  • 解析PHP文件路径相关常量
  • 排序算法--希尔排序
  • Java 大视界 -- Java 大数据在智能教育中的应用与个性化学习(75)
  • 【重生之学习C语言----杨辉三角篇】
  • 如何利用maven更优雅的打包
  • nuxt3中使用useFetch请求刷新不返回数据或返回html结构问题解决-完整nuxt3useFetchtch请求封装
  • [NKU]C++安装环境 VScode
  • C++的 I/O 流
  • CentOS 6.5编译Rsyslog 8.1903.0
  • web3.0技术
  • 计算机组成与接口
  • 天童教育:帮助孩子建立稳定的自信心
  • 如何从0开始做自动化测试?
  • 深度学习系列--03.激活函数
  • Three.js实现炫酷图片粒子化效果:从聚合到扩散的动态演变
  • SystemVerilog系统函数之$system详细使用指南与举例
  • DeepSeek核心关键技术 (冷启动,拒绝采样,蒸馏,多头潜注意力,MoE等) 解读
  • 【Axure高保真原型】中继器表格控制动态面板
  • 不含101的数
  • 微信小程序~django Petting pets(爱抚宠物)小程序
  • 机器学习-线性回归(参数估计之结构风险最小化)
  • JavaScript系列(62)--实时通信系统实现详解
  • 使用page assist浏览器插件结合deepseek-r1 7b本地模型
  • 支持 APQP (先期产品质量策划) 的软件系统-汽车电子行业专用研发管理信息化平台
  • ‌双非硕士的抉择:自学嵌入式硬件开发还是深入Linux C/C++走软开?