组合模式

组合模式的定义:将对象组合成树形结构以表示“部分-整体”的层次结构。而客户端在使用时并不需要区分单个对象(叶子对象)和组合对象。

我们来举一个简单的例子,模拟一下windows的文件系统,比如我们要遍历某一个盘符下的所有文件及文件夹,然后以树状结构输出文件及文件夹的名称。

//先定义一个文件夹类
public class Folder
{  
     //文件夹的名称
     public string Name;    
     
     public Folder(string name)
     {
          Name=name;
     }

    //一个文件夹下可能有很多其他文件夹
    List<Folder> folders=new List<Folder>();
    
    //也可能有很多文件
    List<File> files=new List<File>();
    
    //向该文件夹下添加文件夹
    public void AddFolder(Folder folder)
    {
        folders.Add(folder);
    }

    //移出文件夹下的某个文件夹
    public void RemoveFolder(Folder folder)
    {
        folders.Remove(folder);
    }
    
    //向该文件夹下添加文件
    public void AddFile(File file)
    {
        files.Add(file);
    }
    
    //移出该文件夹下某个文件
    public void RemoveFile(File file)
    {
        files.Remove(file);
    }
    
    //显示该文件夹的名称及其下的文件夹、文件名称
    //文件夹名字前面加“+”,文件名字前面加"-"
    //每向里一层都会在前面加俩空格,用来展示层次结构
    //string的一个构造函数new string(char,num)表示输出num个字符
    public string ShowName(int depth)
    {
        //先显示自己的名字
        Console.WriteLine(new string(' ',depth)+“+”+Name);
        //下一层的名字前要多加俩空格
        depth=depth+2;
        //再显示旗下文件夹的名字
        foreach(var folder in folders)
        {
            folder.ShowName(depth);
        }
        //再显示旗下文件的名字
        foreach(var file in files)
        {
            file.ShowName(depth);
        }
    }
}

//文件类
public class File
{
    public string Name;
    public File(string name)
    {
        Name=name;
    }
    public void string ShowName(int depth)
    {
         Console.WriteLine(new string(' ',depth)+“-”+Name);
    }
}

//客户端
public class Client
{
    public static void Main(string[] args[])
    {
         Folder folder = new Folder("中外文化学习资料");
         Folder folder1_1 = new Folder("中日文化");
         Folder folder1_2 = new Folder("港台文化");
         File file1_1 = new File("傲慢与偏见.pdf");
         File file1_1_1 = new File("苍井空隐退大作.rmvb");
         File file1_2_1 = new File("艳照门.rar");
         folder.AddFolder(folder1_1);
         folder.AddFolder(folder1_2);
         folder.AddFile(file1_1);
         folder1_1.AddFile(file1_1_1);
         folder1_1.AddFile(file1_2_1);
         folder.ShowName(1);
    }
}

+中外文化学习资料
     +中日文化
          -苍井空隐退大作.rmvb
     +港台文化
          -艳照门.rar
     -傲慢与偏见.pdf

虽然上述代码实现了功能,但可以看出必须区分叶子对象(File)与组合对象(Folder),在Folder与Client里都需要区别对待,区别对待组合对象与叶子对象,不仅让程序变得复杂,还对功能的扩展带来不便。比如现在叶子对象不仅有File,又加了一个其他什么叶子对象,部门不仅需要改Client的代码,还要在Folder里再加一个列表及对应的方法。

所以结合组合模式的定义,这是一个“部分-整体”的树型结构,我们只需要保证对叶子对象与组合对象的操作的一致性就可以了。我们来看下其结构图:

组合模式的关键就在于这个抽象类,它既可以代表叶子对象,也可以代表组合对象,这样客户端在操作的时候,不需要区分叶子对象和组合对象,而且在组合对象里也不需要区分。

//由于要统一两种对象的操作,所以抽象类中的方法也主要是两种对象对外方法的和。
//它里面既有叶子对象的方法,也有组合对象的方法。
//常见做法是对于某些子类没有意义的方法,提供默认实现(或抛出异常)
//这样如果子类需要这个方法,那就覆盖实现,不需要就默认父类的实现
public abstract class AbstractClass
{
    //子类的父类实例,也许有些功能会用到
    public AbstractClass parent{get;set;}
    
    //对所有子类都有的方法,定义成抽象方法
    public abstract showName(int depth);
    
     //对不是所有子类都有的方法,提供默认实现
    public add(AbstractClass a)
    {
        throw new UnsupportedOperationException("此对象不支持该方法");
    }
    public remove(AbstractClass a)
    {
        throw new UnsupportedOperationException("此对象不支持该方法");
    }
    ...
    
    public deleteMyself()
    {
        throw new UnsupportedOperationException("此对象不支持该方法");
    }

}


public class Folder:AbstractClass
{  
     //文件夹的名称
     public string Name;    
     
     public Folder(string name)
     {
          Name=name;
     }

    //现在不需要有两个不同的列表了
    List<AbstractClass> childs=new List<AbstractClass>();
    
    //也不需要两类Add方法了
    public void Add(AbstractClass children)
    {
        childs.Add(children);
        //设置子类的父类实例,也许有些功能会用到
        children.parent=this;
    }

    //也不需要两类Remove方法了
    public void Remove(AbstractClass children)
    {
        //这里实现一个功能,就是将某一个节点删除掉的话,将这个节点下的子节点全部移到被删的节点的父节点下面
        foreach(var child in children.getChildren())
        {
            child.parent=this;
            this.childs.add(child);
        }
        childs.Remove(children);
    }

    public List<AbstractClass> getChildren()
    {
        return childs;
    }
         
    //显示该文件夹的名称及其下的文件夹、文件名称
    //文件夹名字前面加“+”,文件名字前面加"-"
    //每向里一层都会在前面加俩空格,用来展示层次结构
    //string的一个构造函数new string(char,num)表示输出num个字符
    public string ShowName(int depth)
    {
        //先显示自己的名字
        Console.WriteLine(new string(' ',depth)+“+”+Name);
        //下一层的名字前要多加俩空格
        depth=depth+2;
        //这里也不需要两个不同的循环了
        foreach(var children in childs)
        {
            children.ShowName(depth);
        }
     }
}

//文件类
public class File
{
    public string Name;
    public File(string name)
    {
        Name=name;
    }
    public void string ShowName(int depth)
    {
         Console.WriteLine(new string(' ',depth)+“-”+Name);
    }
    public void deleteMyself()
    {
    }
}

//客户端
public class Client
{
    public static void Main(string[] args[])
    {
         //客户端只需要Add就可以了不用区分不同的对象
         AbstractClass folder = new Folder("中外文化学习资料");
         AbstractClass folder1_1 = new Folder("中日文化");
         AbstractClass folder1_2 = new Folder("港台文化");
         AbstractClass file1_1 = new File("傲慢与偏见.pdf");
         AbstractClass file1_1_1 = new File("苍井空隐退大作.rmvb");
         AbstractClass file1_2_1 = new File("艳照门.rar");
         folder.Add(folder1_1);
         folder.Add(folder1_2);
         folder.Add(file1_1);
         folder1_1.Add(file1_1_1);
         folder1_1.Add(file1_2_1);
         folder.ShowName(1);
    }
}

+中外文化学习资料
     +中日文化
          -苍井空隐退大作.rmvb
     +港台文化
          -艳照门.rar
     -傲慢与偏见.pdf

我们可以看到在Folder的showName方法里,类似递归,在设计上称作递归关联,与我们通常说的递归算法,递归算法是一个方法会调用方法自己,而这里是一个方法调用子类的同名方法,再在子类里调用子子类的同名方法而已。

组合模式的本质:统一叶子对象和组合对象。


 

posted @ 2015-07-19 10:24  何塞穆里尼奥  阅读(244)  评论(0编辑  收藏  举报