# 组合模式 (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 对象。

# 本质

享元模式的本质:分离与共享。

** 分离的是对象状态中变与不变的部分,共享的是对象中不变的部分。** 享元模式的关键之处就在于分离变与不变,把不变的部分作为享元对象的内部状态,而变化部分就作为外部状态,由外部来维护,这样享元对象就能够被共享,从而减少对象数量,并节省大量的内存空间。