java设计模式学习笔记第五章

合成模式(Composite)

合成模式是一组对象的组合,这些对象可以是容器对象,表现为组的概念;另外一些对象则代表了单对象,或称为叶子对象。在对象组合进行建模时,必须注意两个重要的概念。第一个概念是组对象允许包含单对象,也可以再包含其他的组对象(常见的错误是将组对象设计为只允许包含叶子对象)。第二个概念则是要为组合对象和单对象定义共同的行为。结合这两个概念,就可以为组对象与单对象定义统一的类型,并将该组对象建模为包含同等类型的集合。

合成模式的意图是为了保证客户端调用单对象与组对象的一致性。

 常规组合

  图展示了经典的组合结构。Leaf类和Composite类都实现自一个抽象的Component通用接口,同时Composite对象又包含了其他Composite和Leaf对象的集合。图中Component类是一个抽象类,它为包含任何一个具体方法,因为可以将其定义成接口,让Leaf类和Composite类去实现它。

挑战5.1图中为何Composite类维持了包含Component对象的集合,而不是仅包含叶子对象?

个人答案:

  因为这样设计很灵活,容易扩展。如果仅用叶子对象很难来完成组合对象的构建,那么就需要另一个组合对象来进行扩展。

正确答案:

  

合成模式中的递归行为

  工厂是由车间组成的,每个车间有一条或多条生产线。一条生产线上有很多机器,机器之间互相协作,以保证生产进度。开发人员设计了下图,将这些工厂、车间、生产线看做是“机器”的组合来进行建模。

我对上面这句话的理解是:除了单个机器可以看做是叶子对象。即单个对象。工厂车间生产线都有多个机器,所以这些分别都要看做是组合对象。

 

挑战5.2写出Machine与MachineComposite中getMachineCount()方法的实现代码

个人答案:

 1 class Machine{
 2     public int getMachineCount(){
 3         return 1;
 4     }
 5 }
 6 
class MachineComposite{
    List<Machine> components;
    public int getMachineCount(){
        return components.size();
    }
}

正确答案:

正确答案MachineComposite类里面有一个List,装着Components应该调用每个Component的getMachineCount来计算每个组件中的机器数量。因为这个Components里面的对象可能只是一个机器,那么就返回1;也可能是机器组,有很多机器的生产线,那么返回的就是机器数;也可能是车间数,返回机器数的实现方法里面要实现计算出车间有多少条生产线,生产线上有多少台机器,返回总的机器数。个人觉得递归就这讲述的是这样的过程。

在MachineComponent中,每个方法的定义和操作都是递归的。例如,某个组合中机器的数量是该组合中所有组件包含的机器数量总和。

挑战5.3为MachineComponent声明的每个方法,写出下表中MachineComposite中的递归定义与Machine中的非递归定义。

正确答案:

组合、树与环

  在合成结构中,如果一个节点拥有对其他节点的引用,则该节点就是一棵树。倘若将对象看做节点,将对象间的引用看做边,就可以将对象模型绘制为图形结构。

  考虑一个鉴定或分析化学药品的对象模型。Assay类拥有一个Batch类型的batch属性,Batch类则拥有一个Chemical类型的chemical属性。假设存在一个特殊的Assay对象,它的batch属性引用Batch对象b,b的chemical属性引用chemical对象c。

  a引用b,b引用c,这一系列的引用导致出现了一条从a到c的路径。环(cycle)指的是路径中包含了出现两次的节点。倘若Chemical对象c反向引用Assay对象a的话,将会在对象模型中出现一个环。

下面展示了plant的工厂对象模型,plant是MachineComposite类的对象。这个plant包含一个含有三台机器的车间:mixer、press和assembler。plant对象机器列表组件包含对mixer的直接引用。

以上是非数型的结构。某天工程师报告机器数量有误。我们可以在OozinozFactory类中定义plant()方法,重新创建对象模型。

 1 public static MachineComposite plant(){
 2     MachineComposite plant = new MachineComposite(100);
 3     MachineComposite bay = new MachineComposite(101);
 4     Machine mixer = new Mixer(102);
 5     Machine press = new StarPress(103);
 6     Machine assembler = new ShellAssembler(104);
 7     bay.add(mixer);
 8     bay.add(press);
 9     bay.add(assembler);
10     plant.add(mixer);
11     plant.add(bay);
12     return plant;
13 }

这正是上面创建的plant对象的代码实现。

因此要编写一个isTree()方法检查组合对象是否为树形结构。

倘若在遍历对象模型之后,没有哪个节点被访问两次,我们就认为这个对象模型是一棵数。可以在抽象类MachineComponent中实现isTree()方法,以便它可以维护访问过的节点列表。MachineComponent类可以将代参数的isTree(set:Set)方法作为抽象方法。

在MachineComponent类的实现中,isTree()方法调用了自身的抽象方法isTree(s:Set),代码如下:

1 public boolean eanisTree(){
2     return isTree(new HashSet());
3 }
4 protected abstract boolean isTree(Set s);

该方法使用了java类库的Set类。

Machine和MachineComposite类都必须实现isTree(s:set)方法,Machine类的isTree()实现非常简单,反映了单个机器始终是树形结构:

1 protected boolean isTree(Set visited){
2     visited.add(this);
3     return true;
4 }

MachineComposite实现的isTree()必须把调用它的对象加到visited列表中,并且迭代调用组合对象中各个组件的isTree()方法。如果任何一个被访问过的组件返回false,或者任何一个组件本身不是树形结构,该方法返回false,否则返回true。

挑战5.5写出MachineComposite.isTree(Set visited)的代码

个人答案:

protected boolean isTree(Set visited){
    visited.add(this);
    Iterator i = component.iterator();
    while(i.hasMore()){
        MachineComponent m = (MachineComponent)i.next();
        if(!m.isTree()){
            return false;
        }
    }
    return true;
}

正确答案:

由于是手写,部分的方法调用可能不对。上面应该把hasMore改成hasNext,并且忘了考虑一种情况,当visited.contains(c)即Set集合包含这个对象的时候,也就是在这个Set已经加过此对象。也要返回false。

只需多加注意,防止isTree()返回false,就可以保证对象模型是一个树状模型。另一方面,也可能需要允许组合对象不是树状结构,尤其是当正在建模的问题领域包含环的时候。

含有环的合成模式

  挑战5.4中提到的非树形合成模式是一个失误,原因在于用户将machine同时当做plant和bay的一部分。若是物理对象,可能并不允许它呗包含在多个对象中。然后问题域可以包括多个非物理元素,此时环状包含关系是有意义的。这种场景在对工作流建模时经常发生。

考虑下图描述的礼花弹构造。我们点燃位于底部提供动力的黑火药,形成一股推动力,把火焰弹发射出去。当焰火弹升空后,它的二级引线点燃,最终燃至内核,然后内核燃烧并爆炸,形成火焰的效果。

 

先做内层火焰弹,合格后将引线将内核火焰弹与提供上升动力的部分联系起来。

个人答案:

正确答案:

位于ProcessComponent层级的getStepCount()操作负责统计工艺流程中单个处理步骤的数量。注意,这个数量并非流程的深度,而是图中叶子节点处理步骤的数量。getStepCount()方法必须确保每个步骤的统计只有一次,如果流程中包含了环,应该避免出现死循环。

ProcessComponent类实现了getStepCount()方法,以便通过该方法获得访问过的节点集合。

1 public int getStepCount(){
2     return getStepCount(new HashSet());
3 }
4 public abstract int getStepCount(Set visited);

ProcessComposite类在实现getStepCount()方法时应避免再次访问之前已经访问过的节点:

个人理解。对于上述代码,for循环中pc.getStepCount(visited);如果pc是组合对象,也就是它还有subprocesses,那么调用这个方法会进入递归调用。直到pc为最底层的节点即ProcessStep。返回1。

这个递归有个出口,在visited里面已经存在pc.getName()的值的时候就不会继续调用getStepCount方法了。结果陆续返回了。

 环的影响

  通常,非树形与树形结构组合对象的唯一区别在于,你必须小心谨慎地避免重复操作相同的节点。然而,如果组合对象包含了环,则某些操作是没有意义的。我们不能通过算法觉得制作焰火弹所需的最大步骤。在任何一个包含环的组合对象中,依赖路径长度的操作都是无意义的。因此,尽管我们可以讨论树的告诉--从根到叶子的最长距离,但在一个有环图中却不存在。

小结

  合成模式包含两个相关联的重要概念。一个概念是一个组合对象可以包含单对象或者其他组合对象。与之相关的另一个概念就是组合对象与单对象共享同一个接口。将这些概念应用于对象建模上,就可以在组合对象与单对象上穿件抽象类或Java接口,以此定义公共行为。

  在对组合对象建模时,通常需要给组合节点引入递归定义。倘若存在递归定义,编写代码时,需要注意防止死循环。为避免这一问题,可以确保组合对象都是树形结构。此外,虽然允许环出现在合成模式中,但必须修改算法避免出现死循环。

个人认为:java设计模式第二版对组合模式的讲解理论居多,下面我想参考网上一些对组合模式比较好的例子来展示下这个设计模式的好处。

摘自文章http://blog.csdn.net/hfmbook/article/details/7693069

组合模式解耦了客户程序与复杂元素内部结构,从而使客户程序可以向处理简单元素一样来处理复杂元素。
如果你想要创建层次结构,并可以在其中以相同的方式对待所有元素,那么组合模式就是最理想的选择。本章使用了一个文件
系统的例子来举例说明了组合模式的用途。在这个例子中,文件和目录都执行相同的接口,这是组合模式的关键。通过执行相

同的接口,你就可以用相同的方式对待文件和目录,从而实现将文件或者目录储存为目录的子级元素。

package design.composite;  
/** 
 * 文件名称:design.composite.Company.java 
 *  创建人:Fei Wong  
 *  创建时间: 2012-06-26 
 * 电子邮箱:feiwong8@126.com  
 *  
 */  
public abstract class Company {  
    private String name;  
  
    public Company(String name) {  
        this.name = name;  
    }  
  
    public Company() {  
    }  
  
    public String getName() {  
        return name;  
    }  
  
    public void setName(String name) {  
        this.name = name;  
    }  
  
    protected abstract void add(Company company);  
  
    protected abstract void romove(Company company);  
  
    protected abstract void display(int depth);  
}  
  
  
  
  
package design.composite;  
  
import java.util.ArrayList;  
import java.util.List;  
/** 
 * 文件名称:design.singleton.CommandInvoker.java 
 *  创建人:Fei Wong  
 *  创建时间: 2012-06-26 
 * 电子邮箱:feiwong8@126.com  
 *  
 */  
public class ConcreteCompany extends Company {  
    private List<Company> cList;  
  
    public ConcreteCompany() {  
        cList = new ArrayList<Company>();  
    }  
  
    public ConcreteCompany(String name) {  
        super(name);   
        cList = new ArrayList<Company>() ;   
    }  
  
    @Override  
    protected void add(Company company) {  
        cList.add(company);  
    }  
  
    @Override  
    protected void display(int depth) {  
        // TODO Auto-generated method stub  
        StringBuilder sb = new StringBuilder("");  
        for (int i = 0; i < depth; i++) {  
            sb.append("-");   
        }  
        System.out.println(new String(sb) + this.getName());  
        for (Company c : cList) {  
            c.display(depth + 2);  
        }  
    }  
  
    @Override  
    protected void romove(Company company) {  
        cList.remove(company);  
    }  
}  
  
  
  
package design.composite;  
/** 
 * 文件名称:design.composite.FinanceDepartment.java 
 *  创建人:Fei Wong  
 *  创建时间: 2012-06-26 
 * 电子邮箱:feiwong8@126.com 
 */  
public class FinanceDepartment extends Company {  
      
      
    public FinanceDepartment(){  
          
    }  
      
    public FinanceDepartment(String name){  
        super(name);  
    }  
      
    @Override  
    protected void add(Company company) {  
  
    }  
  
    @Override  
    protected void display(int depth) {  
        StringBuilder sb = new StringBuilder("");  
        for (int i = 0; i < depth; i++) {  
            sb.append("-");  
        }  
        System.out.println(new String(sb) + this.getName() ) ;   
    }  
  
    @Override  
    protected void romove(Company company) {  
          
    }  
      
}  
  
  
  
  
package design.composite;  
/** 
 * 文件名称:design.composite.HRDepartment.java 
 *  创建人:Fei Wong  
 *  创建时间: 2012-06-26 
 * 电子邮箱:feiwong8@126.com 
 */  
public class HRDepartment extends Company {  
      
      
    public HRDepartment(){  
          
    }  
      
    public HRDepartment(String name){  
        super(name);  
    }  
      
    @Override  
    protected void add(Company company) {  
  
    }  
  
    @Override  
    protected void display(int depth) {  
        StringBuilder sb = new StringBuilder("");  
        for (int i = 0; i < depth; i++) {  
            sb.append("-");   
        }  
        System.out.println(new String(sb) + this.getName() ) ;   
    }  
  
    @Override  
    protected void romove(Company company) {  
          
    }  
      
}  
  
  
  
  
package design.composite;  
  
public class Client {  
  
    /** 
     * @param args 
     */  
    public static void main(String[] args) {  
        // TODO Auto-generated method stub  
        Company root = new ConcreteCompany();  
        root.setName("北京总公司");  
        root.add(new HRDepartment("总公司人力资源部"));  
        root.add(new FinanceDepartment("总公司财务部"));  
        Company shandongCom = new ConcreteCompany("山东分公司");  
        shandongCom.add(new HRDepartment("山东分公司人力资源部"));  
        shandongCom.add(new FinanceDepartment("山东分公司账务部"));  
        Company zaozhuangCom = new ConcreteCompany("枣庄办事处");  
        zaozhuangCom.add(new FinanceDepartment("枣庄办事处财务部"));  
        zaozhuangCom.add(new HRDepartment("枣庄办事处人力资源部"));  
        Company jinanCom = new ConcreteCompany("济南办事处");  
        jinanCom.add(new FinanceDepartment("济南办事处财务部"));  
        jinanCom.add(new HRDepartment("济南办事处人力资源部"));   
        shandongCom.add(jinanCom);  
        shandongCom.add(zaozhuangCom);  
        Company huadongCom = new ConcreteCompany("上海华东分公司");  
        huadongCom.add(new HRDepartment("上海华东分公司人力资源部"));  
        huadongCom.add(new FinanceDepartment("上海华东分公司账务部"));  
        Company hangzhouCom = new ConcreteCompany("杭州办事处");  
        hangzhouCom.add(new FinanceDepartment("杭州办事处财务部"));  
        hangzhouCom.add(new HRDepartment("杭州办事处人力资源部"));  
        Company nanjingCom = new ConcreteCompany("南京办事处");  
        nanjingCom.add(new FinanceDepartment("南京办事处财务部"));  
        nanjingCom.add(new HRDepartment("南京办事处人力资源部"));  
        huadongCom.add(hangzhouCom);  
        huadongCom.add(nanjingCom);   
        root.add(shandongCom);  
        root.add(huadongCom);  
        root.display(0);  
    }  
  
}  

这位大神没有对代码进行解释,我看完之后以我个人的经验解释一下。

Company就是公共的接口,不管是叶子还是组合对象都要继承或者实现这个抽象类或者抽象接口,此处他的代码用的是抽象类。public class ConcreteCompany extends Company,具体的公司继承了它,实现了它的add,remove,dispaly等具体操作。这里的ConcreteCompany是组合对象,里面还包含叶子对象,所以展示方法display里面会递归调用。它遍历了cList,cList里面装的是叶子对象,就是最后一层,具体到“办事处”,不能再往下分了,也可能装的是另一个组合对象。遍历cList的时候因为不知道是什么对象。所以不能判断到底到哪一次循环才到叶子对象,才能跳出递归调用。这里用到了方法的重写,因为具体的部门

public class FinanceDepartment extends Company 财务部是不能再细分的了,遍历cList的时候递归到此对象,也就是最后一层的时候(HRDepartment 也是最后一层对象)。调用了财务部的展示方法display。此方法不继续向下递归了,所以就把结果一层一层往回返了。

这里我们看到组合模式确实挺好用,不管是简单的叶子对象,还是复杂的组合对象,我们都可以用相同的方式来处理,因为都继承了Company 抽象类。约束了他们具有的功能。我们开发的时候接触的应用场景可能是创建树的时候能用到,自己编写Tree代码的时候,对节点或者父节点都继承一个相同的接口,实现同样的约束,然后客户端在运用的时候利用多态,返回的就是父类,这里是Company。对于客户端不关心里面的复杂度,只要调用父类规定好的方法即可。

posted on 2016-03-08 13:38  丛兰军  阅读(253)  评论(0)    收藏  举报

导航