Multiple leaf methods problem in composite pattern
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 开发者_JAVA百科any headaches. Interesting problem came out when we tried to implement composite pattern with huge number of methods in leafs.
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.
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.
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.
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).
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.
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.
}
}
}
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.
精彩评论