设计模式——组合模式

来源:互联网 发布:淘宝大学是几本 编辑:程序博客网 时间:2024/06/14 21:21

1 场景问题

1.1 商品类别树

考虑这样一个实际的应用:管理商品类别树。

在实现跟商品有关的应用系统的时候,一个很常见的功能就是商品类别树的管理,比如有如下所示的商品类别树:

- 服装    - 男装        - 衬衣        - 夹克    - 女装        - 裙子        - 套装

仔细观察上面的商品类别树,有以下几个明显的特点:

有一个根节点,比如服装,它没有父节点,它可以包含其它的节点;
树枝节点,有一类节点可以包含其它的节点,称之为树枝节点,比如男装、女装;
叶子节点,有一类节点没有子节点,称之为叶子节点,比如衬衣、夹克、裙子、套装;

现在需要管理商品类别树,假如就要求能实现输出如上商品类别树的结构的功能,应该如何实现呢?

1.2 不用模式的解决方案

要管理商品类别树,就是要管理树的各个节点,现在树上的节点有三类,根节点、树枝节点和叶子节点,再进一步分析发现,

根节点和树枝节点是类似的,都是可以包含其它节点的节点,

把它们称为容器节点。

这样一来,商品类别树的节点就被分成了两种,一种是容器节点,另一种是叶子节点。

容器节点可以包含其它的容器节点或者叶子节点。把它们分别实现成为对象,也就是容器对象和叶子对象,

容器对象可以包含其它的容器对象或者叶子对象,换句话说,容器对象是一种组合对象。

然后在组合对象和叶子对象里面去实现要求的功能就可以了,看看代码实现。

1.先看叶子对象的代码实现,示例代码如下:

/** * 叶子节点对象 */public class Leaf {    // 节点名称    private String name;    // 构造方法 传入叶子名称    public Leaf(String name) {        this.name = name;    }    /**     * 输出叶子对象的结构,叶子对象没有子对象,也就是输出叶子对象的名字     * @param preStr 前缀,主要是按照层级拼接的空格,实现向后缩进     */    public void printStruct(String preStr) {        System.out.println(preStr + "-" + name);    }}

2.再来看看组合对象的代码实现,组合对象里面可以包含其它的组合对象或者是叶子对象,

由于类型不一样,需要分开记录。示例代码如下:

public class Composite {    // 组合节点名称    private String name;    // 子节点集合    private Collection<Leaf> childLeaf = new ArrayList<Leaf>();    // 其他组合节点集合    private Collection<Composite> childComposite = new ArrayList<Composite>();    /**     * 构造方法,传入组合对象的名字     * @param name 组合对象的名字     */    public Composite(String name) {        this.name = name;    }    /**     * 向组合对象加入被它包含的其它组合对象     * @param c 被它包含的其它组合对象     */    public addComposite (Composite c) {        this.childComposite.add(c);    }    /**     * 向组合对象加入被它包含的其它组合对象     * @param c 被它包含的其它组合对象     */    public addLeaf (Leaf l) {        this.childLeaf.add(l);    }    /**     * 输出组合对象自身的结构     * @param preStr 前缀,主要是按照层级拼接的空格,实现向后缩进     */    public void printStruct(preStr) {        //1.先把自己输出去       System.out.println(preStr + "+" + this.name);        //2.然后添加一个空格,表示向后缩进一个空格,输出自己包含的叶子对象       preStr += " ";       for (Leaf leaf : childLeaf) {           leaf.printStruct(preStr);       }       //3.输出当前对象的子对象了       for (Composite composite: childComposite ) {           //递归输出每个子对象           composite.printStruct(preStr);       }    }}

写个客户端来测试一下,看看是否能实现要求的功能,示例代码如下:

public class Client {     public static void main(String[] args) {         // 根节点         Composite root = new Composite("服装");         // 一级节点         Composite c1 = new Composite("男装");         Composite c2 = new Composite("女装");         //定义所有的叶子对象         Leaf leaf1 = new Leaf("衬衣");         Leaf leaf2 = new Leaf("夹克");         Leaf leaf3 = new Leaf("裙子");         Leaf leaf4 = new Leaf("套装");         //按照树的结构来组合组合对象和叶子对象         root.addComposite(c1);         root.addComposite(c2);              c1.addLeaf(leaf1);         c1.addLeaf(leaf2);               c2.addLeaf(leaf3);         c2.addLeaf(leaf4);         //调用根对象的输出功能来输出整棵树         root.printStruct("");     }}

1.3 有何问题

上面的实现,虽然能实现要求的功能,但是有一个很明显的问题:

那就是必须区分组合对象和叶子对象,并进行有区别的对待,

比如在Composite和Client里面,都需要去区别对待这两种对象。

区别对待组合对象和叶子对象,不仅让程序变得复杂,还对功能的扩展也带来不便。

实际上,大多数情况下用户并不想要去区别它们,而是认为它们是一样的,这样他们操作起来最简单。

2 解决方案

2.1 组合模式来解决

仔细分析上面不用模式的例子中,要区分组合对象和叶子对象的根本原因,就在于没有把组合对象和叶子对象统一起来

也就是说,组合对象类型和叶子对象类型是完全不同的类型,这导致了操作的时候必须区分它们。

组合模式通过引入一个抽象的组件对象,作为组合对象和叶子对象的父对象,这样就把组合对象和叶子对象统一起来了,

用户使用的时候,始终是在操作组件对象,而不再去区分是在操作组合对象还是在操作叶子对象。

2.2 组合模式的示例代码

1.组件对象的定义:

/** * Component:抽象的组件对象,为组合中的对象声明接口, * 让客户端可以通过这个接口来访问和管理整个对象结构,可以在里面为定义的功能提供缺省的实现。 */public class Component {    /**     * 示意方法,子组件对象可能有的功能方法     */    public abstract void someOperation();    /**     * 向组合对象中加入组件对象     * @param child 被加入组合对象中的组件对象     */    public void addChild(Component child) {       // 缺省的实现,抛出例外,因为叶子对象没有这个功能,       //或者子组件没有实现这个功能       throw new UnsupportedOperationException("对象不支持这个功能");    }    /**     * 从组合对象中移出某个组件对象     * @param child 被移出的组件对象     */    public void removeChild(Component child) {       // 缺省的实现,抛出例外,因为叶子对象没有这个功能,       //或者子组件没有实现这个功能       throw new UnsupportedOperationException("对象不支持这个功能");    }    /**     * 返回某个索引对应的组件对象     * @param index 需要获取的组件对象的索引,索引从0开始     * @return 索引对应的组件对象     */    public Component getChildren(int index) {       // 缺省的实现,抛出例外,因为叶子对象没有这个功能,       //或者子组件没有实现这个功能       throw new UnsupportedOperationException("对象不支持这个功能");    }}

2.Composite对象的定义:

/** * 组合对象,通常需要存储子对象,定义有子部件的部件行为, * 并实现在Component里面定义的与子部件有关的操作 */public class Composite extends Component {     /**     * 用来存储组合对象中包含的子组件对象     */    private List<Component> childComponents = null;    /**     * 示意方法,通常在里面需要实现递归的调用     */    public void someOperation() {            if (childComponents != null){           for(Component c : childComponents){              //递归的进行子组件相应方法的调用              c.someOperation();           }       }    }    public void addChild(Component child) {       //延迟初始化       if (childComponents == null) {           childComponents = new ArrayList<Component>();       }       childComponents.add(child);    }    public void removeChild(Component child) {        if (childComponents != null) {           childComponents.remove(child);        }    }    public Component getChildren(int index) {       if (childComponents != null){           if(index>=0 && index<childComponents.size()){              return childComponents.get(index);           }       }       return null;    }}

3.叶子对象的定义了,相对而言比较简单,示例代码如下:

public class Leaf extends Component {   /**     * 示意方法,子组件对象可能有的功能方法     */    public void someOperation() {        // do something    }}

4.对于Client,就是使用Component接口来操作组合对象结构,由于使用方式千差万别,

这里仅仅提供一个示范性质的使用,顺便当作测试代码使用,示例代码如下:

public class Client {    public static void main(String[] args) {        //定义多个Composite对象       Component root = new Composite();       Component c1 = new Composite();       Component c2 = new Composite();       //定义多个叶子对象       Component leaf1 = new Leaf();       Component leaf2 = new Leaf();       Component leaf3 = new Leaf();       //组合成为树形的对象结构       root.addChild(c1);       root.addChild(c2);       root.addChild(leaf1);       c1.addChild(leaf2);       c2.addChild(leaf3);       //操作Component对象       Component o = root.getChildren(1);       System.out.println(o);    }}

2.3 使用组合模式重写示例

1.首先就是要为组合对象和叶子对象添加一个抽象的父对象做为组件对象,

在组件对象里面,定义一个输出组件本身名称的方法以实现要求的功能,示例代码如下:

/** * 抽象的组件对象 */public abstract class Component {    /**     * 输出组件自身的名称     */    public abstract printStrcut(String preStr);   /**     * 向组合对象中加入组件对象     * @param child 被加入组合对象中的组件对象     */    public void addChild(Component child) {       throw new UnsupportedOperationException("对象不支持这个功能");    }    /**     * 从组合对象中移出某个组件对象     * @param child 被移出的组件对象     */    public void removeChild(Component child) {       throw new UnsupportedOperationException("对象不支持这个功能");    }    /**     * 返回某个索引对应的组件对象     * @param index 需要获取的组件对象的索引,索引从0开始     * @return 索引对应的组件对象     */    public Component getChildren(int index) {       throw new UnsupportedOperationException("对象不支持这个功能");    }}

2.先看叶子对象的实现,它变化比较少,只是让叶子对象继承了组件对象,

其它的跟不用模式比较,没有什么变化,示例代码如下:

/** * 叶子节点对象 */public class Leaf extends Component {    // 节点名称    private String name;    // 构造方法 传入叶子名称    public Leaf(String name) {        this.name = name;    }    /**     * 输出叶子对象的结构,叶子对象没有子对象,也就是输出叶子对象的名字     * @param preStr 前缀,主要是按照层级拼接的空格,实现向后缩进     */    public void printStruct(String preStr) {        System.out.println(preStr + "-" + name);    }}

3.接下来看看组合对象的实现,这个对象变化就比较多,大致有如下的改变:

public class Composite {    /**     * 用来存储组合对象中包含的子组件对象     */    private List<Component> childComponents = null;     /**     * 组合对象的名字     */    private String name = "";    /**     * 构造方法,传入组合对象的名字     * @param name 组合对象的名字     */    public Composite(String name) {        this.name = name;    }    public void addChild(Component child) {       //延迟初始化       if (childComponents == null) {           childComponents = new ArrayList<Component>();       }       childComponents.add(child);    }    /**     * 输出组合对象自身的结构     * @param preStr 前缀,主要是按照层级拼接的空格,实现向后缩进     */    public void printStruct(String preStr){       //先把自己输出去       System.out.println(preStr+"+"+this.name);       //如果还包含有子组件,那么就输出这些子组件对象       if(this.childComponents!=null){           //然后添加一个空格,表示向后缩进一个空格           preStr+=" ";                //输出当前对象的子对象了           for(Component c : childComponents){              //递归输出每个子对象              c.printStruct(preStr);           }       }    }}

4.客户端也有变化,客户端不再需要区分组合对象和叶子对象了,
统一都是使用组件对象,调用的方法也都要改变成组件对象定义的方法。示例代码如下:

public class Client {    public static void main(String[] args) {       //定义所有的组合对象       Component root = new Composite("服装");       Component c1 = new Composite("男装");       Component c2 = new Composite("女装");       //定义所有的叶子对象       Component leaf1 = new Leaf("衬衣");       Component leaf2 = new Leaf("夹克");       Component leaf3 = new Leaf("裙子");       Component leaf4 = new Leaf("套装");       //按照树的结构来组合组合对象和叶子对象       root.addChild(c1);       root.addChild(c2);       c1.addChild(leaf1);       c1.addChild(leaf2);       c2.addChild(leaf3);       c2.addChild(leaf4);       //调用根对象的输出功能来输出整棵树       root.printStruct("");    }}

2.4 文件案例

1.抽象组件

public interface IFile {    //下面两个方法,相当于类图中operation方法    void delete();    String getName();    /* 以上为公共行为,以下为文件夹才有的行为 */    //创建新文件,相当于add方法    void createNewFile(String name);    //相当于remove方法    void deleteFile(String name);    //相当于GetChild方法    IFile getIFile(int index);}

2.文件

//文件public class File implements IFile{    private String name;    private IFile folder;    public File(String name,IFile folder) {        super();        this.name = name;        this.folder = folder;    }    public String getName() {        return name;    }    public void delete() {        folder.deleteFile(name);        System.out.println("---删除[" + name + "]---");    }    //文件不支持创建新文件    public void createNewFile(String name) {        throw new UnsupportedOperationException();    }    //文件不支持删除文件    public void deleteFile(String name) {        throw new UnsupportedOperationException();    }    //文件不支持获取下面的文件列表    public IFile getIFile(int index) {        throw new UnsupportedOperationException();    }}

3.文件夹

//文件夹public class Folder implements IFile{    private String name;    private IFile folder;    private List<IFile> files;    public Folder(String name) {        this(name, null);    }    public Folder(String name,IFile folder) {        super();        this.name = name;        this.folder = folder;        files = new ArrayList<IFile>();    }    public String getName() {        return name;    }    //与File的删除方法不同,先删除下面的文件以及文件夹后再删除自己    public void delete() {        List<IFile> copy = new ArrayList<IFile>(files);        System.out.println("------------删除子文件-------------");        for (IFile file : copy) {            file.delete();        }        System.out.println("----------删除子文件结束-------------");        if (folder != null) {            folder.deleteFile(name);        }        System.out.println("---删除[" + name + "]---");    }    public void createNewFile(String name) {        if (name.contains(".")) {            files.add(new File(name,this));        }else {            files.add(new Folder(name,this));        }    }    public void deleteFile(String name) {        for (IFile file : files) {            if (file.getName().equals(name)) {                files.remove(file);                break;            }        }    }    public IFile getIFile(int index) {        return files.get(index);    }}

4.客户端

public class Client {    public static void main(String[] args) {        IFile root = new Folder("我的电脑");        root.createNewFile("C盘");        root.createNewFile("D盘");        root.createNewFile("E盘");        IFile D = root.getIFile(1);        D.createNewFile("project");        D.createNewFile("电影");        IFile project = D.getIFile(0);        project.createNewFile("test1.java");        project.createNewFile("test2.java");        project.createNewFile("test3.java");        IFile movie = D.getIFile(1);        movie.createNewFile("致青春.avi");        movie.createNewFile("速度与激情6.avi");        /* 以上为当前文件系统的情况,下面我们尝试删除文件和文件夹 */        display(null, root);        System.out.println();        project.delete();        movie.getIFile(1).delete();        System.out.println();        display(null, root);    }    //打印文件系统    public static void display(String prefix,IFile iFile){        if (prefix == null) {            prefix = "";        }        System.out.println(prefix + iFile.getName());        if(iFile instanceof Folder){            for (int i = 0; ; i++) {                try {                    if (iFile.getIFile(i) != null) {                        display(prefix + "--", iFile.getIFile(i));                    }                } catch (Exception e) {                    break;                }            }        }    }}

参考:http://www.jianshu.com/p/dead42334033

原创粉丝点击