# 组合模式 (Composite)
# 背景
在计算机的文件系统中,有 “文件夹” 的概念 (在有些操作系统中,也称为 “目录”)。文件夹里面既可以放人文件,也可以放人其他文件夹(子文件夹)。在子文件夹中,一样地既可以放入文件,也可以放入子文件夹。可以说,文件夹是形成了一种容器结构、递归结构。我们接着再想一想。虽然文件夹与文件是不同类型的对象,但是它们都 “可以被放入到文件夹中”。文件夹和文件有时也被统称为 “目录条目”( directory entry)。在目录条目中,文件夹和文件被当作是同一种对象看待(即一致性)。例如,想查找某个文件夹中有什么东西时,找到的可能是文件夹,也可能是文件。简单地说,找到的都是目录条目。有时,与将文件夹和文件都作为目录条目看待一样,将容器和内容作为同一种东西看待,可以帮助我们方便地处理问题。在容器中既可以放入内容,也可以放入小容器,然后在那个小容器中,又可以继续放人更小的容器。这样,就形成了容器结构、递归结构。在本章中,我们要学习的 Composite 模式就是用于创造出这样的结构的模式。能够使容器与内容具有一致性,创造出递归结构的模式就是 Composite 模式。Composite 在英文中是 “混合物”“复合物” 的意思。
# 总结
将对象组合成树形结构以表示 “整体 - 部分” 的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
** 组合模式通过引入一个抽象的组件对象,作为组合对象和叶子对象的父对象,这样就把组合对象和叶子对象统一起来了,用户使用的时候,始终是在操作组件对象,而不再去区分是在操作组合对象还是在操作叶子对象。** 组合模式的关键就在于这个抽象类,这个抽象类既可以代表叶子对象,也可以代表组合对象,这样用户在操作的时候,对单个对象和组合对象的使用就具有了一致性。
# 结构
Component:抽象的组件对象,为组合中的对象声明接口,让客户端可以通过这个接口来访问和管理整个对象结构,可以在里面为定义的功能提供缺省的实现。
Leaf:叶子节点对象,定义和实现叶子对象的行为,不再包含其它的子节点对象。
Composite:组合对象,通常会存储子组件,定义包含子组件的那些组件的行为,并实现在组件接口中定义的与子组件有关的操作。
Client:客户端,通过组件接口来操作组合结构里面的组件对象。
# 本质
组合模式的本质:统一叶子对象和组合对象。
组合模式通过把叶子对象当成特殊的组合对象看待,从而对叶子对象和组合对象一视同仁,统统当成了 Component 对象,有机的统一了叶子对象和组合对象。
正是因为统一了叶子对象和组合对象,在将对象构建成树形结构的时候,才不需要做区分,反正是组件对象里面包含其它的组件对象,如此递归下去;也才使得对于树形结构的操作变得简单,不管对象类型,统一操作。
# 实例
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);
@Override
public String toString() {
return getName() + "(" + getSize() + ")";
}
}
public class File extends Entry { | |
private String name; | |
private int size; | |
public File(String name, int size) { | |
this.name = name; | |
this.size = size; | |
} | |
@Override | |
public String getName() { | |
return name; | |
} | |
@Override | |
public int getSize() { | |
return size; | |
} | |
@Override | |
protected void printList(String prefix) { | |
System.out.println(prefix+"/"+this); | |
} | |
} |
public class Directory extends Entry { | |
private String name; | |
private ArrayList directory = new ArrayList(); | |
public Directory(String name) { | |
this.name = name; | |
} | |
@Override | |
public String getName() { | |
return name; | |
} | |
@Override | |
public int getSize() { | |
int size = 0; | |
Iterator iterator = directory.iterator(); | |
while (iterator.hasNext()) { | |
Entry next = (Entry) iterator.next(); | |
size += next.getSize(); | |
} | |
return size; | |
} | |
@Override | |
public Entry add(Entry entry) { | |
directory.add(entry); | |
return this; | |
} | |
@Override | |
protected void printList(String prefix) { | |
System.out.println(prefix + "/" + this); | |
Iterator iterator = directory.iterator(); | |
while (iterator.hasNext()){ | |
Entry entry = (Entry) iterator.next(); | |
entry.printList(prefix + "/" + this); | |
} | |
} | |
} |
public class FileTreatmentException extends RuntimeException { | |
public FileTreatmentException() { | |
} | |
public FileTreatmentException(String msg) { | |
super(msg); | |
} | |
} |
# 装饰 (Decorator)
# 背景
假如现在有一块蛋糕,如果只涂上奶油,其他什么都不加,就是奶油蛋糕。如果加上草莓,就是草莓奶油蛋糕。如果再加上一块黑色巧克力板,上面用白色巧克力写上姓名,然后插上代表年龄的蜡烛,就变成了一块生日蛋糕。
不论是蛋糕、奶油蛋糕、草莓蛋糕还是生日蛋糕,它们的核心都是蛋糕。不过,经过涂上奶油,加上草莓等装饰后,蛋糕的味道变得更加甜美了,目的也变得更加明确了。
程序中的对象与蛋糕十分相似。首先有一个相当于蛋糕的对象,然后像不断地装饰蛋糕一样地不断地对其增加功能,它就变成了使用目的更加明确的对象。
# 总结
动态地给一个对象添加一些额外的职责。就增加功能来说,装饰模式比生成子类更为灵活。
虽然经过简化,业务简单了很多,但是需要解决的问题不会少,还是要解决:要透明的给一个对象增加功能,并实现功能的动态组合。
所谓透明的给一个对象增加功能,换句话说就是要给一个对象增加功能,但是不能让这个对象知道,也就是不能去改动这个对象。而实现了能够给一个对象透明的增加功能,自然就能够实现功能的动态组合,比如原来的对象有 A 功能,现在透明的给它增加了一个 B 功能,是不是就相当于动态组合了 A 和 B 功能呢。
在装饰模式的实现中,为了能够和原来使用被装饰对象的代码实现无缝结合,是通过定义一个抽象类,让这个类实现与被装饰对象相同的接口,然后在具体实现类里面,转调被装饰的对象,在转调的前后添加新的功能,这就实现了给被装饰对象增加功能,这个思路跟 “对象组合” 非常类似。
在转调的时候,如果觉得被装饰的对象的功能不再需要了,还可以直接替换掉,也就是不再转调,而是在装饰对象里面完全全新的实现。
# 结构
Component:组件对象的接口,可以给这些对象动态的添加职责。在示例程序中,由 Display 类扮演此角色。
ConcreteComponent:具体的组件对象,实现组件对象接口,通常就是被装饰器装饰的原始对象,也就是可以给这个对象添加职责。在示例程序中,由 StringDisplay 类扮演此角色。
Decorator:所有装饰器的抽象父类,需要定义一个与组件接口一致的接口,并持有一个 Component 对象,其实就是持有一个被装饰的对象。注意,这个被装饰的对象不一定是最原始的那个对象了,也可能是被其它装饰器装饰过后的对象,反正都是实现的同一个接口,也就是同一类型。在示例程序中,由 Border 类扮演此角色
ConcreteDecorator:实际的装饰器对象,实现具体要向被装饰对象添加的功能。在示例程序中,由 SideBorder 类和 FulllBorder 类扮演此角色。
# 本质
装饰模式的本质:功能细化,动态组合。
** 动态是手段,组合才是目的。** 这里的组合有两个意思,一个是动态功能的组合,也就是动态进行装饰器的组合;另外一个是指对象组合,通过对象组合来实现为被装饰对象透明的增加功能。
但是要注意,装饰模式不仅仅可以增加功能,也可以控制功能的访问,可以完全实现新的功能,还可以控制装饰的功能是在被装饰功能之前还是之后来运行等。
总之,装饰模式是通过把复杂功能简单化,分散化,然后在运行期间,根据需要来动态组合的这么一个模式。
# 实例
public abstract class Display {
public abstract int getColumns();
public abstract int getRows();
public abstract String getRowText(int row);
public final void show() {
for (int i = 0; i < getRows(); i++) {
System.out.println(getRowText(i));
}
}
}
public class StringDisplay extends Display {
private String string;
public StringDisplay(String string) {
this.string = string;
}
@Override
public int getColumns() {
return string.getBytes(StandardCharsets.UTF_8).length;
}
@Override
public int getRows() {
return 1;
}
@Override
public String getRowText(int row) {
if (row == 0) {
return string;
} else {
return null;
}
}
}
public abstract class Border extends Display {
protected Display display;
protected Border(Display display) {
this.display = display;
}
}
public class FullBorder extends Border {
protected FullBorder(Display display) {
super(display);
}
@Override
public int getColumns() {
return 1 + display.getColumns() + 1;
}
@Override
public int getRows() {
return 1 + display.getRows() + 1;
}
@Override
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) {
StringBuffer buf = new StringBuffer();
for (int i = 0; i < count; i++) {
buf.append(ch);
}
return buf.toString();
}
}
public class SideBorder extends Border {
private char borderChar;
public SideBorder(Display display, char borderChar) {
super(display);
this.borderChar = borderChar;
}
@Override
public int getColumns() {
return 1 + display.getColumns() + 1;
}
@Override
public int getRows() {
return display.getRows();
}
@Override
public String getRowText(int row) {
return borderChar + display.getRowText(row) + borderChar;
}
}
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();
}
}
# 享元模式
运用共享技术有效地支持大量细粒度的对象。
# 结构
Flyweight:享元接口,通过这个接口 flyweight 可以接受并作用于外部状态。通过这个接口传入外部的状态,在享元对象的方法处理中可能会使用这些外部的数据。
ConcreteFlyweight:具体的享元实现对象,必须是可共享的,需要封装 flyweight 的内部状态。
UnsharedConcreteFlyweight:非共享的享元实现对象,并不是所有的 Flyweight 实现对象都需要共享。非共享的享元实现对象通常是对共享享元对象的组合对象。
FlyweightFactory:享元工厂,主要用来创建并管理共享的享元对象,并对外提供访问共享享元的接口。
Client:享元客户端,主要的工作是维持一个对 flyweight 的引用,计算或存储享元对象的外部状态,当然这里可以访问共享和不共享的 flyweight 对象。
# 本质
享元模式的本质:分离与共享。
** 分离的是对象状态中变与不变的部分,共享的是对象中不变的部分。** 享元模式的关键之处就在于分离变与不变,把不变的部分作为享元对象的内部状态,而变化部分就作为外部状态,由外部来维护,这样享元对象就能够被共享,从而减少对象数量,并节省大量的内存空间。