reupe

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

面向对象编程中,对象之间存在着种种关系,比如has-a, has-a关系又叫组合关系,比如公司和部门之间,集体和个人之间等等。公司一般都会有呈树状的组织架构,有时候,希望使用树状结构来表达整体和部分之间的关系,使客户能够不加以区分地处理结构中的每一个对象,这时候就会用到----组合模式

 


1.组合模式

组合模式(Composite pattern) 描述一组对象,这些对象以相同方式作为同一类对象的单个实例进行处理。

 组合模式的意图在于一致地处理整体和部分,所以,并不一定只要是hasA关系就可以使用组合模式的,只有“组件”和“零件”之间存在类似的行为时且具有明显的树形结构时,使用组合模式才是合适的。以下是组合模式的UML类图:

             

上图中,Client直接面向抽象接口Component来处理元素,不必区分到底是Leaf节点还是Composite节点。

Leaf节点: 叶子结点, 没有子节点,直接实现抽象接口Component的display()方法,作为终止方法;

Composite节点:复合节点,有一个或多个子节点,它持有一个列表来保存其子节点,将请求转发给每一个子节点。

组合模式的树形结构,与解释器模式类似,参考解释器模式

 


 

2.透明模式和安全模式

透明模式:Component中定义子节点相关操作的抽象方法(add、remove等),使客户端一致地处理Leaf和Composite对象;但是由于Leaf节点没有子节点,所以这种方式类型安全问题需要注意。

安全模式:子节点相关操作的方法(add,remove等),只定义在Composite对象中,保证了类型安全;但是需要客户端分别处理两种对象,也就失去了一致性。

 一般,相比类型安全来说,组合模式更加强调一致性

下面的例子使用透明模式为例。 

 


3.实现XML文档创建

3.1 设计

组合模式一般应用于有明显层级结构的对象上,比如公司的组织架构,目录结构等等。XML(可扩展标记语言)是开发中常常用来作为配置文件来使用,本例中我尝试应用组合模式来实现一个简单的XML结构的文档,文档根元素名称为root, 子元素可随意命名,子元素可包含文本,结构如下:

<root>
  <A>
    <A1>
      组合模式就是好!
    <A1/>
  <A/>
  <B>
    对待每个对象都一样!
  <B/>
<root/>

文档有明显的层次结构,元素可分为两类:1.复合元素(包活根元素), 2.文本,  设计类图如下:

DocumentHelper是一个工具类,提供了一系列静态方法来创建、修改、打印文档。

 

3.2代码实现

文档抽象类, 使用透明模式,定义了子节点相关的方法add,remove, 定义子类统一需要实现的方法display(int indent), indent是为了打印美观而设置的缩进。

abstract class Document {

    public abstract void add(Document document);
    public abstract void remove(Document document);
    public abstract String display(int indent);
}

 

叶子结点---文本元素的实现, 注意到文本对象由于没有子节点,所以add,remove方法不支持;

display方法返回该文本元素的文本字符串,包含缩进。

class Text extends Document {

    private String content;
    public Text(String content) {
        this.content = content;
    }

    @Override
    public void add(Document document) {
        throw new UnsupportedOperationException("不支持次操作");
    }

    @Override
    public void remove(Document document) {
        throw new UnsupportedOperationException("不支持次操作");
    }

    @Override
    public String display(int indent) {
        StringBuilder builder = new StringBuilder();
        //缩进距离
        for (int i = 0; i < indent; i++) {
            builder.append(" ");
        }
        //叶子元素直接输出内容
        builder.append(content + "\n");
        return builder.toString();
    }
}

 

复合节点对象实现,Element对象包含子节点,维护一个List,存放其子节点,并重写add,remove方法来设置子节点;

display方法中,实现了<E>xxxx</E>这种形式的结构,实现方式是在<></>之间,调用了其子节点的display方法。

class Element extends Document {

    private String name;

    private List<Document> documents = new ArrayList<>();

    public Element(String name) {
        this.name = name;
    }

    @Override
    public void add(Document document) {
        documents.add(document);
    }

    @Override
    public void remove(Document document) {
        documents.remove(document);
    }

    @Override
    public String display(int indent) {
        StringBuilder builder = new StringBuilder();
        //缩进距离
        for (int i = 0; i < indent; i++) {
            builder.append(" ");
        }
        //元素开始标签
        builder.append("<" + name + ">\n");
        //子元素
        for (Document document : documents) {
            builder.append(document.display(indent + 2));
        }
        //结束标签缩进距离
        for (int i = 0; i < indent; i++) {
            builder.append(" ");
        }
        //元素结束标签
        builder.append("<" + name + "/>\n");
        return builder.toString();
    }
}

 

 工具类,帮助生成、修改、打印文档的元素

class DocumentHelper {

    public static Document createRootDocument() {
        Document root = new Element("root");
        return root;
    }

    public static Document createElement(String name) {
        return new Element(name);
    }

    public static Document createText(String content) {
        return new Text(content);
    }

    public static void printDocument(Document root) {
        String xml = root.display(0);
        System.out.println(xml);
    }
}

 

客户端生成文档并打印:

public class MyXml {
    public static void main(String[] args) {
        //创建根元素
        Document root = DocumentHelper.createRootDocument();
        //创建两个子元素A合B
        Document a = DocumentHelper.createElement("A");
        Document b = DocumentHelper.createElement("B");
        //存入子元素到根元素
        root.add(a);
        root.add(b);
        //在子元素A下再创建一个子元素A1
        Document a1 = DocumentHelper.createElement("A1");
        a.add(a1);
        //在子元素A1下创建文本
        Document text = DocumentHelper.createText("组合模式就是好!");
        a1.add(text);
        //在B元素下创建文本
        Document text2 = DocumentHelper.createText("对待每个对象都一样!");
        b.add(text2);
        //显示xml
        DocumentHelper.printDocument(root);
    }
}

 

输出结果

<root>
  <A>
    <A1>
      组合模式就是好!
    <A1/>
  <A/>
  <B>
    对待每个对象都一样!
  <B/>
<root/>

 

 多亏了多态,树形结构中的每一个对象都能够以相同的处理方式对待,因为我们只面向接口而不依赖实现,这是依赖倒置原则的体现啊。

 


4.总结

组合模式解决一下问题: 

  • 怎样表示整体-部分层级结构,从而使客户能一致地作用于层级中的每个对象;
  • 整体-部分层级结构如何表示成树状结构。

具体实现的方式:

  • 定义一个Component接口,Leaf和Composite实现, Composite中维护一个List用于保存子节点。
  • Leaf对象直接实现客户请求,Composite对象将请求转发给子节点。

在透明模式和安全模式的选择中,组合模式更加强调一致性,从而透明模式更具有“普世价值”。

posted on 2019-02-26 17:45  yxlaisj  阅读(326)  评论(0编辑  收藏  举报