Michael W.
Michael W.

Reputation: 101

What to do if classes with same interface having similar but different method signature?

What to do if classes with same interface having similar but different method signature?

Let's say I have a project to calculate different costs (to get a total cost at last).

In my program, there is several calculator classes, namely ACostCalculator, BCostCalculator and so on. When a calculate() method is invoked to calculate a cost, a cost container is passed to those cost calculators too. In a good scenario, I can make a CostCalculator Interface for every cost calculators.

However, the calculation for different cost required different resources. In my current program, it make be like:

//getResource() are costly method while several costs need this. So do it outside calculate() method.
ResourceA resourceA = getResourceA(); 
ResourceB resourceB = getResourceB();

CostContainer costContainer = new CostContainer();
CostCalculator aCostCalculator = new ACostCalculator();
...
CostCalculator eCostCalculator = new ECostCalculator();

aCostCalculator.calculate(costContainer);
bCostCalculator.calculate(costContainer)
cCostCalculator.calculate(costContainer, resourceA);
dCostCalculator.calculate(costContainer, resourceA);
eCostCalculator.calculate(costContainer, resourceA, resourceB);

If the signature is exactly the same, I may make a loop conveniently to do it at once. However, since they are similar but different, I can't even make a good interface.

I am not sure if there is good ways to do so. What I can think of is generalizing all calculate() method to into

calculate(CostContainer costContainer, List<Object> resources);

Any ideas? Thanks for answering.

Upvotes: 7

Views: 331

Answers (4)

Fuhrmanator
Fuhrmanator

Reputation: 12882

The problem of varying signatures for a common interface sounds a lot like the problem that the Adapter design pattern (object adapter variant) solves:

Adapter pattern (GoF)

Applied to your situation, you'd only use adapters to the non-conforming calculators. There are really only two types of adapters, Type1 for signature (costContainer, resourceA) and Type2 for signature (costContainer, resourceA, resourceB). Using your example:

Adapter pattern applied to your example

Advantages of adapter are that it's a known design pattern (published by GoF in 1995), it allows eventual calculate(...) methods that have varying signatures. Adapters can be updated dynamically if context changes occur (e.g. resources change).

Disadvantages are obviously the additional classes, the indirection, etc. It's more complicated than the selected answer, but is more flexible particularly if you can't modify the API of the adaptees.

Upvotes: 2

Paweł Głowacz
Paweł Głowacz

Reputation: 3046

This is something that you actually use with Generics:

    Resource resourceA = new ResourceA();
    Resource resourceB = new ResourceB();
    CostContainer costContainer = new CostContainer();
    CostCalculator<Resource> costCalculatorA = new ACostCalculator();
    costCalculatorA.calculate(costContainer,resourceA,resourceB);
    CostCalculator<Resource> costCalculatorB = new BCostCalculator();
    costCalculatorB.calculate(costContainer,resourceA);


interface Resource {
    //Your code
}

class ResourceA implements Resource {
    //Your code
}

class ResourceB implements Resource {
    //Your code
}

class CostContainer {
    //Your code
}

interface CostCalculator<T extends Resource> {
    void calculate(CostContainer costContainer, T... resources);
}

class ACostCalculator implements CostCalculator<Resource>{

    @Override
    public void calculate(CostContainer costContainer, Resource... resources) {
        System.out.println("Test");
    }
}

class BCostCalculator implements CostCalculator<Resource>{

    @Override
    public void calculate(CostContainer costContainer, Resource... resources) {
        System.out.println("Test2");
    }
}

Upvotes: -1

Glorfindel
Glorfindel

Reputation: 22631

You can use variadic arguments:

public interface CostCalculatorInterface {
    public void calculate(CostContainer container, Object... resources);
}

(or replace Object with another superclass of ResourceA and ResourceB).

In the classes implementing the interface, resources will be an Object[] so you can refer to them as resources[0], resources[1] and so on.

Upvotes: 0

Lesmana
Lesmana

Reputation: 27043

If the resources stay the same for the lifetime of the calculators: pass the resources to the constructor of the calculators.

ResourceA resourceA = getResourceA(); 
ResourceB resourceB = getResourceB();

CostContainer costContainer = new CostContainer();

CostCalculator aCostCalculator = new ACostCalculator();
CostCalculator bCostCalculator = new BCostCalculator();
CostCalculator cCostCalculator = new CCostCalculator(resourceA);
CostCalculator dCostCalculator = new DCostCalculator(resourceA);
CostCalculator eCostCalculator = new ECostCalculator(resourceA, resourceB);

aCostCalculator.calculate(costContainer);
bCostCalculator.calculate(costContainer);
cCostCalculator.calculate(costContainer);
dCostCalculator.calculate(costContainer);
eCostCalculator.calculate(costContainer);

Upvotes: 5

Related Questions