views:

43

answers:

2

My application is an editor for connecting "modules" (through module ports). Ports have port types. Each port type has it's associated comparator. Two types are compatible if their attributes satisfy rules implemented in the comparator.

I want users to extend the application by implementing new port types and rules how to connect them (through the eclipse extension point infrastructure).

This implies that during runtime I can only interact with the port type interfaces. No concrete classes are known. All concrete implementations are returned by a factory.

I implemented this two interfaces:

public interface IPortType {
    [many attributes]
}
public interface IComparator {
    public boolean isCompatible(IPortType source, IPortType target);

    // more interaction methods!
}

My current solution is a bit ugly, since the generic isCompatible(IPortType source, IPortType target) method is a kind of delegate and has to be rewritten in all subclasses. Simply overloading the isCompatible() method does not work here.

But a bigger disadvantage is the violation of the open-closed-principle: One must extend all concrete Comparator classes when a new type should be supported. But how to keep the number of rule classes low, when there are more interactions between types like conversion, etc.? My intention was to keep all rules for one type in one class.

A concrete Comparator example:

public class ATypeComparator implements IComparator {

    public boolean isCompatible(IPortType source, IPortType target) {
        if (!(source instanceof AType))
            return false;
        if (target instanceof BType)
            return isCompatible(source, (BType) target);
        if (target instanceof CType)
            return isCompatible(source, (CType) target);
    }

    public boolean isCompatible(AType source, BType target) {...}
    public boolean isCompatible(AType source, CType target) {...}
}

How would you solve this problem?

Thanks for any suggestions!

+1  A: 

I don't think it is right to an IPortType implementation decide whether it is compatible with other IPortType implementations. That's just not part of its responsibility.

A simple solution would be to create a single, public static method, for instance in a class PortTypeManager, that knows whether two IPortType implementation are compatible. That way, you can always add a new type and you only have to change logic in one place to accommodatie that new type.

However, in the end, this will also not be enough, because the number of cases that method should cover grows like n^2. You need to supply each IPortType implementation with a method like getVersion() or getSignature(), which returns a piece of data you can compare against a similar piece of data, to decide whether two implementations are compatible.

Confusion
I agree that the compatibility check is not part of the type's responsibility. It also leads to many places to make changes when new types arise.The compatibility rules depend on a subset of attributes a type can have. There are combinatoric and arithmetic test to solve. A generic data structure that handles those operations could end up in implementing another "programming language". But your idea of a signature is quite interesting...
Joker
A: 

It would seem that your implementation could be cleaned up if you allowed for polymorphism to handle the complexities.

Would it not be simpler to have a .compatibleTo() method on the IPortType? If you could do this, each implementation could know intrinsically what it supporting end points it could support?

Something along the lines of:

IPortType port1 = ...
IPortType port2 = ...

if (port.compatibleTo(port2)) {
    // do whatever
}
gpampara
I agree that this solution is less complex, but it leads to changes in all types compatible to each other. And there is no possibility to change existing port types' code, since the eclipse philosophy is extend not change. Also the rules of compatibility should be separated from a port type, since it is not its responsibility to know what all can be done with it.
Joker
Is compatibility reflexive? If `port1` is compatible with `port2`, must it then be the case that `port2` is compatible with `port1`?
seh
It should be reflexive. In any case, it seems to be a matter of perspective. If adding a new concrete implementation of an interface results in more work than what should be expected, it can usually safely be taken that there is a code smell and some refactoring would possibly be a good idea.
gpampara
Yes, I think compatibility is reflexive here. But there are still O(n^2) rules for n types in worst case.
Joker