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

时间:2022-01-16 11:59:59

At work, we are developing an PHP application that would be later re-programmed into Java. With some basic knowledge of Java, we are trying to design everything to be easily re-written, without any headaches. Interesting problem came out when we tried to implement composite pattern with huge number of methods in leafs.

在工作中,我们正在开发一个PHP应用程序,稍后将其重新编程到Java中。有了Java的一些基本知识,我们正在尝试设计一切易于重写的东西,没有任何问题。当我们尝试用大量的方法来实现复合模式时,出现了一个有趣的问题。

What are we trying to achieve (not using interfaces, it's just a quick example):

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

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( );

It seems like pretty much classic Composite pattern, but problem is that we will have quite many leaf classes and each of them might have ~5 methods (of which few might be different than others). One of our solutions, which seems to be the best one so far and might actually work, is using __call magic method to call methods in leafs. Unfortunately, we don't know if there is an equivalent of it in Java.

看起来很像典型的复合模式,但问题是我们将会有很多的叶子类,每个叶子类可能有~5个方法(其中很少有可能与其他不同)。我们的解决方案之一是使用__call magic方法来调用leafs中的方法,这似乎是迄今为止最好的解决方案,而且可能确实有效。不幸的是,我们不知道Java中是否有类似的东西。

So the actual question is: Is there a better solution for this, using code that would be eventually easily re-coded into Java? Or do you recommend any other solution? Perhaps there's some different, better pattern I could use here.

所以实际的问题是:是否有更好的解决方案,使用最终很容易被重新编码到Java中的代码?或者你推荐其他的解决方案吗?也许我可以在这里使用一些不同的、更好的模式。

In case there's something unclear, just ask and I'll edit this post.

如果有什么不清楚的地方,只要问我,我会编辑这篇文章。

Edit:

编辑:

Actual problem is that not every leaf class contains, for example, method Baz. If we used simple foreach to call Baz in every class, it'd give use bunch of errors, as there are certain classes that don't contain this method. Classic solution would be to have every single method from every single leaf class implemented into Composite class, each with different implementation. But this would make our composite class huge and messy with amount of methods we use.

实际的问题是并不是每个leaf类都包含Baz方法。如果我们在每个类中使用简单的foreach调用Baz,就会产生大量的错误,因为有些类不包含这个方法。经典的解决方案是将每个leaf类中的每个方法都实现为复合类,每个方法都有不同的实现。但这将使我们的复合类在使用大量方法时变得庞大而混乱。

So usual solution would look like this (Composite class):

通常的解决方案是这样的(复合类):

class Composite implements Fooable, Bazable {
    ...

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

    public function Baz( ) {
        ...
    }
}

To prevent our code to become real mess, we were thinking about something like:

为了防止我们的代码变得一团糟,我们考虑了以下问题:

class Composite {
    ...

    public function __call( ) {
        // implementation
    }
}

But we aren't really sure if it's a good solution and if there's something similar also in Java (as asked already before edit).

但是我们不确定这是否是一个好的解决方案,Java中是否也有类似的东西(编辑之前已经问过了)。

2 个解决方案

#1


2  

Within Java you could consider using the visitor pattern whereby you pass a visitor object to each node in the tree and the node makes a callback to the visitor class to determine which behaviour should be performed.

在Java中,您可以考虑使用visitor模式,通过该模式将visitor对象传递到树中的每个节点,节点对visitor类进行回调,以确定应该执行哪些行为。

This avoids any casting or explicitly checking the type of each node.

这避免了强制转换或显式地检查每个节点的类型。

/**
 * 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.
    }
  }
}

#2


2  

Visitor design pattern is a quite good solution. But you have to consider possible changes in the structure, e.g. new Leaf class will make you implement applyVisitor and add visit* method to every other Visitor you have created. So Visitor really helpts you to add behaviour to structured objects at price of that structure not changing too often. If the structure changes often and the algorithms not so much, you might consider having different composites for objects with same interfaces. If you want to do it the dirty way as you currently do in PHP, look at Java reflection API. Nice solution would be imho dynamic calls (as in Ruby or Python). You can simulate those, but that would be much work... So my answer is use the Visitor with care or consider different Composites for objects with different behaviour.

访问者设计模式是一个很好的解决方案。但是您必须考虑结构中可能的更改,例如,新的Leaf类将使您实现applyVisitor并向您创建的每个其他访问者添加visit*方法。访问者真的可以帮助你在结构不经常变化的前提下增加结构对象的行为。如果结构经常变化,算法也不那么频繁,那么您可以考虑为具有相同接口的对象使用不同的组合。如果您想按照当前在PHP中使用的方式进行操作,请查看Java反射API。很好的解决方案是imho动态调用(如在Ruby或Python中)。你可以模拟它们,但那将是很多工作……所以我的答案是谨慎地使用访问者,或者考虑不同的复合对象具有不同的行为。

#1


2  

Within Java you could consider using the visitor pattern whereby you pass a visitor object to each node in the tree and the node makes a callback to the visitor class to determine which behaviour should be performed.

在Java中,您可以考虑使用visitor模式,通过该模式将visitor对象传递到树中的每个节点,节点对visitor类进行回调,以确定应该执行哪些行为。

This avoids any casting or explicitly checking the type of each node.

这避免了强制转换或显式地检查每个节点的类型。

/**
 * 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.
    }
  }
}

#2


2  

Visitor design pattern is a quite good solution. But you have to consider possible changes in the structure, e.g. new Leaf class will make you implement applyVisitor and add visit* method to every other Visitor you have created. So Visitor really helpts you to add behaviour to structured objects at price of that structure not changing too often. If the structure changes often and the algorithms not so much, you might consider having different composites for objects with same interfaces. If you want to do it the dirty way as you currently do in PHP, look at Java reflection API. Nice solution would be imho dynamic calls (as in Ruby or Python). You can simulate those, but that would be much work... So my answer is use the Visitor with care or consider different Composites for objects with different behaviour.

访问者设计模式是一个很好的解决方案。但是您必须考虑结构中可能的更改,例如,新的Leaf类将使您实现applyVisitor并向您创建的每个其他访问者添加visit*方法。访问者真的可以帮助你在结构不经常变化的前提下增加结构对象的行为。如果结构经常变化,算法也不那么频繁,那么您可以考虑为具有相同接口的对象使用不同的组合。如果您想按照当前在PHP中使用的方式进行操作,请查看Java反射API。很好的解决方案是imho动态调用(如在Ruby或Python中)。你可以模拟它们,但那将是很多工作……所以我的答案是谨慎地使用访问者,或者考虑不同的复合对象具有不同的行为。