邯郸网站建设的地方,取消网站备案流程,网站如何做内部链接,python基础教程资料十一、Composite模式#xff1a;容器与内容的一致性
像文件夹与文件一样#xff0c;文件夹中可以放子文件夹与文件#xff0c;再比如容器中可以放更小的容器和具体内容。
Composite模式#xff1a;使容器与内容具有一致性#xff0c;创造出递归结构。
Composite#x…十一、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 模式通过整体地替换算法来改变类的功能。