Normally, comparators are the best option when you want to compare objects using different attributes (see for example How to compare objects by multiple fields). However, in my particular case I'm not sure about using comparators.
The problem is the following: I have defined a generic interface, called Node<S>
, which is shared
by different components. There is also a CostNode<S>
which extends the Node<S>
, and a ScoreNode<S>
which extends CostNode<S>
:
public interface Node<S> {
S getS();
// more methods...
}
public interface CostNode<S> extends Node<S> {
// This method smells really bad
int compareByCost(ComparableNode<S> node);
}
public interface ScoreNode<S> extends CostNode<S> {
// int compareByCost(CostNode<S> node) (from CostNode<S>)
int compareByScore(ScoreNode<S> node);
}
At this point, someone can argue: You don't need CostNode and ScoreNode, you can use different comparators to compare nodes. That's ok. But the "problem" comes now:
I have a component, called Client, which uses ScoreNodes. Client requires a node factory, provided by the user, which is responsible of creating ScoreNodes:
public class Client {
// ...
public Client(NodeFactory<S, ScoreNode<S>> nodeFactory){...}
public void process() {
while(...){
S current = get();
S old = getOld();
// ...
ScoreNode<S> next = this.nodeFactory.create(current,...));
// Comparisons performed
if (next.compareByCost(old) <=0){
//...
}
if (next.compareByScore(old) > 0){
// ...
}
}
}
}
As you can see, the behavior of comparing nodes is embedded into the nodes, and is closely related to the factory used (different nodes requires a different factory and different comparators).
On the other hand, if I use comparators, I have to provide to the Client three components:
CostComparator, ScoreComparator and a NodeFactory. In this scenario, I can use only Node<S>
and forget about CostNode<S>
and ScoreNode<S>
:
public class ConcreteNodeCostComparator implements Comparator<Node<S>> {
public int compare(Node<S> a, Node<S> b){
return Double.compare(((ConcreteNode<S>)a).getCost(), ((ConcreteNode<S>)b).getCost());
}
}
public class ConcreteNodeScoreComparator implements Comparator<Node<S>> {
public int compare(Node<S> a, Node<S> b){
return Double.compare(((ConcreteNode<S>)a).getScore(), ((ConcreteNode<S>)b).getScore());
}
}
However, I don't really like this alternative, because in this case I have to provide two more components to Client, when the comparison methods strongly depends on the nodes.
I think I'm missing something in this design. What do you think about?