Composite模式

来源:互联网 发布:手机暖手宝软件 编辑:程序博客网 时间:2024/04/27 15:11

在面向对象的系统中,我们经常会遇到一类具有“容器”特征的对象,即它们在充当对象的同时,又是其他对象的容器。

举例:在操作系统中,文件的概念很广泛,其中文件可以是普通文件,也可以是目录(Unix中,设备也是文件),目录中可以存放文件。如下图:

 

1

 

File                     :抽象的文件类

PhysicalFile      :普通文件

Directory            :目录

 

// File.java

package com.pnft.patterns.composite;

 

public abstract class File

{

    public abstract void process();

}

 

// PhysicalFile.java

package com.pnft.patterns.composite;

 

public class PhysicalFile extends File

{

    @Override

    public void process()

    {

        System.out.println("Method process() in PhysicalFile...");

    }

}

 

// Directory.java

package com.pnft.patterns.composite;

 

import java.util.ArrayList;

 

public class Directory extends File

{

    private ArrayList<File> arrayList;

 

    @Override

    public void process()

    {

        System.out.println("Method process() in Directory...");

    }

   

    public Directory()

    {

        arrayList = new ArrayList<File>();

    }

   

    public ArrayList<File> getFiles()

    {

        return arrayList;

    }

   

    public void add(File file)

    {

        arrayList.add(file);

    }

   

    public void remove(File file)

    {

        if(file != null)

        {

            arrayList.remove(file);

        }

    }

}

 

// PatternClient.java

package com.pnft.patterns.composite;

 

import java.util.ArrayList;

import java.util.Iterator;

 

public class PatternClient

{

    private void recursiveProcess(ArrayList<File> arrayList)

    {

        if (arrayList != null)

        {

            for(Iterator<File> iter = arrayList.iterator(); iter.hasNext();)

            {

                File f = iter.next();

                if(f instanceof Directory)

                {

                    ((File)f).process();

                    recursiveProcess(((Directory) f).getFiles());

                }

                else

                {

                    f.process();

                }

            }

        }

    }

   

    public static void main(String[] args)

    {

        PatternClient pc = new PatternClient();

        File f = new Directory();

        f.process();

       

        File f1 = new Directory();

        File f2 = new PhysicalFile();

   

        ((Directory)f1).add(f2);

        ((Directory)f).add(f1);

       

        File f3 = new Directory();

        File f4 = new Directory();

        File f5 = new Directory();

        File f6 = new PhysicalFile();

   

        ((Directory)f5).add(f6);

        ((Directory)f4).add(f5);

        ((Directory)f3).add(f4);

        ((Directory)f).add(f3);

       

        ArrayList<File> arrayList = ((Directory)f).getFiles();

        pc.recursiveProcess(arrayList);

    }

}

上述程序运行结果:

Method process() in Directory...

Method process() in Directory...

Method process() in PhysicalFile...

Method process() in Directory...

Method process() in Directory...

Method process() in Directory...

Method process() in PhysicalFile...

 

上面程序存在的问题:

在容器类Directory中存在一个getFiles方法,而该方法的存在直接导致了客户程序PatternClient中的recursiveProcess方法的存在,这说明Directory类和客户程序PatternClient的耦合太紧(另外还有,在PatternClient中要判断一个对象是Directory还是PhysicalFile,以便采取不同的处理方式等等,都说明了耦合过紧这一情况),而且recursiveProcess方法是递归的,这于程序的维护不甚合适。因此,问题的根源在于:客户代码过多地依赖对象容器的内部实现结构,对象容器内部实现结构(而非抽象接口)的变化将引起客户端嗲吗的频繁变化,从而带来了代码的维护性和扩展性等方面的弊端。

 

Composite设计模式就是将“客户代码与复杂的对象昂容器结构”解耦,让对象容器自己来实现自身的复杂结构,从而使得客户代码就像处理简单对象(PhysicalFile)一样来处理复杂的对象容器(Directory)

 

Composite设计模式的意图就是,将对象组合成属性结构以表示“部分-整体”的层次结构。它使得客户对单个对象和组合对象的使用具有一致性。-GoF

 

下面我们用Composite设计模式来解决上面的问题,具体代码如下,其中被注解的部分是未采用Composite设计模式之前的代码:

// File.java

package com.pnft.patterns.composite;

 

public abstract class File

{

    public abstract void process();

    public abstract void add(File file);        // add方法抽象出来

    public abstract void remove(File file); // remove方法抽象出来

}

 

// PhysicalFile.java

package com.pnft.patterns.composite;

 

public class PhysicalFile extends File

{

    @Override

    public void process()

    {

        System.out.println("Method process() in PhysicalFile...");

    }

 

    @Override

    public void add(File file)

    {

        // add方法在本类中完全没有必要,但是为了将Directory类中的add方法抽取到File中,

        // 因此必须在本类override这个add方法,这是为了在客户程序中不需要区分到底是

        // Directory类还是PhysicalFile类,从而减少客户程序的依赖。

       

        // 这个方法可以为空,或者抛出一个自定义的异常。

       

        // 不很完美,因为在本类中被迫增加了这个方法。

    }

 

    @Override

    public void remove(File file)

    {

        // remove方法在本类中完全没有必要,但是为了将Directory类中的remove方法抽取到File中,

        // 因此必须在本类override这个add方法,这是为了在客户程序中不需要区分到底是

        // Directory类还是PhysicalFile类,从而减少客户程序的依赖。

       

        // 这个方法可以为空,或者抛出一个自定义的异常。

       

        // 不很完美,因为在本类中被迫增加了这个方法。

    }

}

 

// Directory.java

package com.pnft.patterns.composite;

 

import java.util.ArrayList;

import java.util.Iterator;

 

public class Directory extends File

{

    private ArrayList<File> arrayList;

   

    @Override

    public void process()

    {

        System.out.println("Method process() in Directory...");

        // 下面if块是按照Composite设计模式的要求新增加的代码

        if(arrayList != null)

        {

            for(Iterator<File> iter = arrayList.iterator(); iter.hasNext();)

            {

                File f = (File)iter.next();

                f.process();

            }

        }

    }

   

    public Directory()

    {

        arrayList = new ArrayList<File>();

    }

   

//  public ArrayList<File> getFiles()       // 注释掉

//  {

//      return arrayList;

//  }

   

    public void add(File file)

    {

        arrayList.add(file);

    }

   

    public void remove(File file)

    {

        if(file != null)

        {

            arrayList.remove(file);

        }

    }

}

 

// PatternClient.java

package com.pnft.patterns.composite;

 

//import java.util.ArrayList;       // 注释掉

//import java.util.Iterator;

 

public class PatternClient

{

//  private void recursiveProcess(ArrayList<File> arrayList)

//  {

//      if (arrayList != null)

//      {

//          for(Iterator<File> iter = arrayList.iterator(); iter.hasNext();)

//          {

//              File f = iter.next();

//              if(f instanceof Directory)

//              {

//                  ((File)f).process();

//                  recursiveProcess(((Directory) f).getFiles());

//              }

//              else

//              {

//                  f.process();

//              }

//          }

//      }

//  }

   

    public static void main(String[] args)

    {

//      PatternClient pc = new PatternClient();

        File f = new Directory();

//      f.process();

       

        File f1 = new Directory();

        File f2 = new PhysicalFile();

   

//      ((Directory)f1).add(f2);        // 注释掉这2行代码,

//      ((Directory)f).add(f1);         // 用下面两行代码代替

        f1.add(f2);

        f.add(f1);

       

        File f3 = new Directory();

        File f4 = new Directory();

        File f5 = new Directory();

        File f6 = new PhysicalFile();

   

//      ((Directory)f5).add(f6);        // 注释掉这4行代码,

//      ((Directory)f4).add(f5);

//      ((Directory)f3).add(f4);

//      ((Directory)f).add(f3);         // 用下面4行代码代替

        f5.add(f6);

        f4.add(f5);

        f3.add(f4);

        f.add(f3);

       

        f.process();

       

//      ArrayList<File> arrayList = ((Directory)f).getFiles(); // 注释掉

//      pc.recursiveProcess(arrayList);

    }

}

 

各对象之间的关系:

图2 

从上面程序看,PatternClient已经被简化了许多,而且仅对抽象类File有依赖(创建对象除外,创建对象可以用诸如Simple Factory设计模式来解决,在此不赘述)。上面程序运行结果:

Method process() in Directory...

Method process() in Directory...

Method process() in PhysicalFile...

Method process() in Directory...

Method process() in Directory...

Method process() in Directory...

Method process() in PhysicalFile...

 

可以看到,该结果和前面程序的结果是一致的。前后两个程序的重要区别:

1. 递归并没有消除,后者将递归移动到了Directory类中的process方法,同时虽然是递归,但是非常自然,易于理解;而前者需要客户程序来处理递归,从而从代码维护的角度而言存在很大的差别。

2. 后者将add方法和remove方法抽象到了File类中,从而保证在客户程序中不需要进行转型(Type Casting)处理,从而大幅度降低了客户程序代码复杂程度和对外部依赖程序。这样做的一个glitch就是,在PhysicalFile类中被迫增加了add方法和remove方法。

 

下面是后者的UML类图:

 

图3

 

实际上严格的Composite静态结构图如下:

 

4

 

同时一定要记住:

Leaf中的add方法和remove方法,必须为空(即不存在任何代码)或者只抛出异常即可。因为Leaf类是叶子类,不能作为容器,因此也就无从add或者remove了。

 

我们可以看到,图1的大致结构看起来和图2、图3没有太大的不同,它们之间的主要区别是:

1. 一些方法是否需要存在

2. 这些方法所处的位置

3. 方法内容不同

 

因此不能仅从静态结构图比较类似,就判定对应图1是实现的项目代码就符合Composite设计模式的。通过前面两个不同的Java项目的代码,可以非常清楚地看出这一点。

原创粉丝点击