Question

Consider the code snippet

public interface Car
{
    string getColor();
    void Drive();
}

public class CarWithAutomaticTransition : Car
{
    public string getColor() { return "Red"; }
    public void Drive()
    {
        // Drive implementation...
    }
}

Now consider a car with a manual transmission.

public interface IShiftable
{
    void upShift();
    void downShift();
}

public CarWithManualTransmission : Car, IShiftable
{
    public string getColor() { return "Green"; }
    public void drive()
    {
        // Drive implementation
    }

    public void upShift()
    {
        // upShift concrete implementation...
    }

    public void downShift()
    {
        // downShift concrete implementation...
    }
}

It sounds stupid, but in efforts to reduce the number of "if's" in code, I've been reading about the null object pattern where (in this example) the IShiftable interface could actually be implemented in the automatic transition, but with no implementation. The alternative is to do a run-time-type-check on the object to see if it implements IShiftable.

The problems (tradeoffs) I see are the NullObject implementation seems to be somewhat misleading in that there's an interface with no implementation. However, you don't have to do run-time type checking and could just call that method if you need to.

The interface-specific implementation seems too specialized in that it's difficult to have one client handle all Car implementations be they manual or automatic transmission.

Is there an option I'm overlooking here, or is it a design problem and if so, an example would be appreciated.

Was it helpful?

Solution

You can reasonably make a case for either solution, but it would probably depend on the actual situation in code. Its hard to state a preference using the strawman "Car" example.

Since upShift and downShift are both no argument side effecting functions, deciding whether to perform those side effects as part of an if or as part of a method dispatch are strictly equivalent.

Depending on your specific language, there are some refinements you could make though.

For one, you can state that Shiftable things must also be Cars

public interface Car {
    String getColor();
    void drive();
}

public interface Shiftable extends Car {
    void downShift();
    void upShift();
}

This way when you test for the ability to shift, you know that the thing you have will have all the other related car methods.

You could also do this at the interface/trait/"polymorphism defining thing" level with some analogy to default methods.

public interface Car {
    String getColor();
    void drive();
    default void upShift() {}
    default void downShift() {}
}

In languages without nominal static types, the common idiom is to test for "capabilities" - usually the presence of duck typed methods on an object - rather than for specific concrete inheritance. So you can also just check for if upShift exists and if downShift exists and if so call them.

if car.responds_to(:up_shift)
  car.up_shift
end

(I'm sure there are more, but this is all that popped into my head atm)

Licensed under: CC-BY-SA with attribution
scroll top