一、Composite--容器与内容的一致性
1. 场景分析
2. UML
3. Code
3.1 抽象父类,一致性的承担者
/**
* @author CSZ
*/
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() + ")";
}
}
3.2 继承自 Entry 重写了其中的方法
/**
* @author CSZ
*/
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);
}
}
3.3 也继承自 Entry 而且扮演容器角色
/**
* @author CSZ
*/
public class Directory extends Entry{
private String name;
// 书中源码没有加入泛型,我认为这里可以直接使用 <Entry> 这样下文中的 Iterator也可以添加泛型而不用强转
private List 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 entry = (Entry)iterator.next();
size += entry.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 + "/" + name);
}
}
}
3.4
/**
* @author CSZ
*/
public class FileTreatmentException extends RuntimeException{
public FileTreatmentException() {
}
public FileTreatmentException(String message) {
super(message);
}
}
3.5 测试类
/**
* @author CSZ
*/
public class MainTest {
public static void main(String[] args) {
try {
System.out.println("创建根目录");
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("创建用户目录下");
Directory yuki = new Directory("yuki");
Directory hanakao = new Directory("hanakao");
Directory tomura = new Directory("tomura");
usrdir.add(yuki);
usrdir.add(hanakao);
usrdir.add(tomura);
yuki.add(new File("diary.html",100));
hanakao.add(new File("Composite.java",200));
rootdir.printList();
} catch (FileTreatmentException e){
e.printStackTrace();
}
}
}
4. 总结与思考
其实这个关于内容与容器的一致性问题,我们如果使用 linux 的话感受非常明显,而且前面有一个模式中也偷偷使用这个模式,你能不能发现呢?如果没有印象了,告诉你去抽象工厂中找找。
所以没有什么需要太多解释了,不过有一点,我们一定要重视,这个示例中,点睛之笔就是 getName 和 getSize 两个方法,我们为什么可以递归调用,这就是内容和容器的一致性的表现。
二、Decorator 模式--装饰边框与被装饰物的一致性
1. 需求分析
2. UML
3. Code
3.1 Display
/**
* @author CSZ
*/
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));
}
}
}
3.2 StringDisplay
/**
* @author CSZ
*/
public class StringDisplay extends Display{
private String string;
public StringDisplay(String string) {
this.string = string;
}
@Override
public int getColumns() {
return string.getBytes().length;
}
@Override
public int getRows() {
return 1;
}
@Override
public String getRowText(int row) {
if (row == 0){
return string;
} else {
return null;
}
}
}
3.3 Border
/**
* @author CSZ
*/
public abstract class Border extends Display{
protected Display display;
protected Border(Display display){
this.display = display;
}
}
3.4 SideBorder
/**
* @author CSZ
*/
public class SideBorder extends Border{
private char borderChar;
public SideBorder(Display display,char ch){
super(display);
this.borderChar = ch;
}
@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;
}
}
3.5 FullBorder
/**
* @author CSZ
*/
public class FullBorder extends Border{
public 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 buffer = new StringBuffer();
buffer.append(String.valueOf(ch).repeat(Math.max(0, count)));
return buffer.toString();
}
}
3.6 MainTest
/**
* @author CSZ
*/
public class MainTest {
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("Peace And Love")),'*'))),'/');
b4.show();
}
}
4. 总结与分析
除了图中的描述分析,还有一些扩展思路
- 当我们用巧克力进行了装饰后,得到的仍然是一个蛋糕,这是由于我们的装饰边框与被装饰物具有一致性,体现为 无论是 ConcreteComponent 还是 ConcreteDecorator 都是 Component 的子类。
- 当我们裹上厚厚的巧克力酱后,我们仍然可以计算新的蛋糕的半径和高度,也就是被装饰物和装饰物具有相同的接口API,则体现了接口的透明性。
- 同时,我们还可以继续撒上糖果,撒上草莓,而我们的蛋糕本体并不需要因为新的变动而变动,这就得益于我们的一致性。更有意思的是再撒上糖果后,我们还可以再涂上巧克力(只要你你不怕变胖)。
- 在 java 中 i/o 的处理就是根据这种模式。