复合模式中的多叶方法问题

时间:2010-04-23 11:23:57

标签: java php design-patterns

在工作中,我们正在开发一个PHP应用程序,稍后可以将其重新编程为Java。有了Java的一些基本知识,我们正在尝试设计一些易于重写的内容,而不会有任何麻烦。当我们尝试在叶子中使用大量方法实现composite pattern时,出现了有趣的问题。

我们想要实现什么(不使用接口,这只是一个简单的例子):

class Composite {
    ...
}


class LeafOne {
    public function Foo( );

    public function Moo( );
}


class LeafTwo {
    public function Bar( );

    public function Baz( );
}


$c = new Composite( Array( new LeafOne( ), new LeafTwo( ) ) );

// will call method Foo in all classes in composite that contain this method
$c->Foo( );

// same with Bar
$c->Bar( );

它似乎是非常经典的复合模式,但问题是我们将拥有相当多的叶类,并且它们中的每一个可能有~5种方法(其中很少可能与其他方法不同)。我们的解决方案之一似乎是迄今为止最好的解决方案,可能实际上正在使用__call magic method来调用叶子中的方法。 不幸的是,我们不知道Java中是否有相同的东西。

所以实际的问题是:有没有更好的解决方案,使用最终可以轻松重新编码为Java的代码?或者您推荐任何其他解决方案?也许我可以在这里使用一些不同的,更好的模式。

如果有不清楚的地方,请询问,我会编辑这篇文章。

修改

实际问题是并非每个叶类都包含例如方法Baz。如果我们使用简单的foreach在每个类中调用Baz,它会使用一堆错误,因为某些类不包含此方法。经典的解决方案是将每个叶子类中的每个方法都实现为Composite类,每个方法都有不同的实现。但这会使我们的复合类庞大而且混乱我们使用的方法。

通常的解决方案看起来像这样(复合类):

class Composite implements Fooable, Bazable {
    ...

    public function Foo( ) {
        foreach( $this->classes as $class ) {
            $class->Foo( );
        }
    }

    public function Baz( ) {
        ...
    }
}

为了防止我们的代码变得混乱,我们考虑的事情是:

class Composite {
    ...

    public function __call( ) {
        // implementation
    }
}

但是我们并不确定它是否是一个很好的解决方案,如果在Java中也有类似的东西(在编辑之前已经提到过)。

2 个答案:

答案 0 :(得分:2)

在Java中,您可以考虑使用visitor模式,将访问者对象传递给树中的每个节点,节点对访问者类进行回调,以确定应该采用哪种行为被执行。

这可以避免任何转换或显式检查每个节点的类型。

/**
 * Visitor capable of visiting each node within a document.
 * The visitor contains a callback method for each node type
 * within the document.
 */
public interface DocumentNodeVisitor {
  void visitWord(Word word);
  void visitImage(Image img);
}

/**
 * Base interface for each node in a document.
 */
public interface DocumentNode {
  void applyVisitor(DocumentVisitor v);
}

/**
 * Conrete node implementation representing a word.
 */    
public class Word implements DocumentNode {
  private final String s;

  public Word(String s) { this.s = s; }

  public String getValue() { return this.s; }

  public void applyVisitor(DocumentVisitor v) {
    // Make appropriate callback to visitor.
    v.visitWord(this);
  }
}

/**
 * Conrete node implementation representing an image.
 */        
public class Image implements DocumentNode {
  public void applyVisitor(DocumentVisitor v) {
    // Make appropriate callback to visitor.
    v.visitImage(this);
  }
}

public class Paragraph implements DocumentNode {
  private final List<DocumentNode> children;

  public Paragraph() {
    this.children = new LinkedList<DocumentNode>();
  }

  public void addChild(DocumentNode child) {
    // Technically a Paragraph should not contain other Paragraphs but
    // we allow it for this simple example.
    this.children.add(child);
  }

  // Unlike leaf nodes a Paragraph doesn't callback to
  // the visitor but rather passes the visitor to each
  // child node.
  public void applyVisitor(DocumentVisitor v) {
    for (DocumentNode child : children) {
      child.applyVisitor(v);
    }
  }
}    

/**
 * Concrete DocumentVisitor responsible for spell-checking.
 */
public class SpellChecker implements DocumentVisitor
  public void visitImage(Image i) {
    // Do nothing, as obviously we can't spellcheck an image.
  }

  public void visitWord(Word word) {
    if (!dictionary.contains(word.getValue()) {
      // TODO: Raise warning.
    }
  }
}

答案 1 :(得分:2)

访客设计模式是一个非常好的解决方案。但是你必须考虑结构的可能变化,例如新的Leaf类将使您实现applyVisitor并将visit *方法添加到您创建的每个其他访问者。因此,访客确实可以帮助您以不太经常变化的结构为结构对象添加行为。如果结构经常更改而算法没那么多,您可能会考虑为具有相同接口的对象使用不同的复合。如果您想像在PHP中一样使用 dirty 方式,请查看Java反射API。好的解决方案是imho动态调用(如在Ruby或Python中)。你可以模拟那些,但这将是很多工作...所以我的答案是小心使用访问者或考虑不同行为的对象的不同复合材料。