0

I'm trying to write a library function(f) that uses another weight function(w) in it's implementation. I want to have a default weight function(dw) in use but also to allow users of the library function(f) to provide their own weight function(w).

I made an interface for weight functions that has a calculate function. However, because I don't know what parameters such function would require, I defined it like this:

public interface WeightFunction {
    double calculate(Object ... arguments);
}

However, when I override it with my default function(dw), I did this ugly thing:

 @Override
    public double calculate(Object ... arguments) {
        return calculate((Pixel)arguments[0], (Pixel)arguments[1]);
    }

    public double calculate(Pixel u, Pixel v){
        //logic here
        return 0;
    }

Is there a more elegant way to do this? Is this considered good form?

Yonatan S
  • 23
  • 7
  • This code would throw a `ClassCastException` at runtime if `argument[0]` or `argument[1]` is an object that is not an `instanceof Pixel`. Is that intended? – Turing85 Aug 11 '20 at 15:32
  • If using the default weight function, yes. Pixel is an interface, so it should throw a class exception if those aren't an object that ```implements Pixel``` – Yonatan S Aug 11 '20 at 15:42
  • „*...I'm trying to write a library function(f) that uses another weight function(w)...*“ — A rough *sketch* of what you mean by „_`function(f)` **uses** `function(w)`_“ would be helpful. Please share pseudocode — *or something* — of your _`function(f)`_? TIA. – deduper Aug 12 '20 at 12:17

2 Answers2

3

You might like to use Generics?

public interface WeightFunction<T> {
    double calculate(T ... arguments);
}

class A implements WeightFunction<Pixel> {

    @Override
    public double calculate(Pixel... arguments) {
        return calculate(arguments[0], arguments[1]);
    }

    public double calculate(Pixel u, Pixel v){
        //logic here
        return 0;
    }
}

You could also just use a single argument and allow the caller to wrap all his arguments in a class. This might be better in case you have arguments of multiple different types.

public interface WeightFunction<T> {
    double calculate(T argument);
}

@Override
public double calculate(SeededPixels arg) {
    return calculate(arg.u, arg.v); // * arg.seed
}

class SeededPixels {
    public final Pixel u;
    public final Pixel v;
    public final long seed;

    SeededPixels(Pixel u, Pixel v, long seed) {
        this.u = u;
        this.v = v;
        this.seed = seed;
    }
}
Gregor Koukkoullis
  • 2,255
  • 1
  • 21
  • 31
  • This would limit the weight function to be able to T arguments of the same type. What if a weight function takes in two pixels and a random seed that is represented by a long? – Yonatan S Aug 11 '20 at 15:43
  • You might want to consider to use just one argument and provide a class wrapping the multiple fields of different types. In my opinion this is much cleaner then a list a arguments of different type. – Gregor Koukkoullis Aug 11 '20 at 15:50
  • That looks good. So in order to maintain the decoupling between my function and the arguments the class would have no fields or methods, right? It exists solely as an empty container for the whatever arguments future users will use. – Yonatan S Aug 11 '20 at 16:10
  • More or less yes. The typical approach could be that users create a simple value class where all fields are final. Methods are not strictly necessary, fields could be public but they could also provide getter methods if they like. Of course the specific calculate function would need to know how to access the fields. – Gregor Koukkoullis Aug 11 '20 at 16:23
  • I've marked this as correct as this answers my question the best. However I decided that my idea was bad practice and I should change my general design. Thank you for your input! – Yonatan S Aug 12 '20 at 17:03
1

Generics is the way to go. But interpreting this in your question:

However, because I don't know what parameters such function would require, I defined it like this:

and your first comment to answer from Gregor Koukkoullis I think that your problem is that you just need to (and should) declare every method that takes different amount of parameters. There just is no other way around but it is anyway more clear this way.

So you should have something like this:

public interface WeightFunction<T> {
    double calculate(T... arguments);
    double calculate(Long seed, T... arguments);
    double calculate(Long seed, Integer somethingElse, T... arguments);
}

Why varags parameters have to be the last? See this. The accepted answer propably is not the clearest one but few others will clarify the problem.

Now, when you implemented your calculate in your example somehow you knew what are the parameters?

@Override
public double calculate(Object ... arguments) {
    // you know here that the 2 first are pixels, dont you?
    return calculate((Pixel)arguments[0], (Pixel)arguments[1]);
}

So with the same knowledge you could just create a declaration of needed atributes in your interface. Maybe even:

double calculate(Long seed, T t1, T t2);

if there is more likely only two Ts.

And the answer to your question:

Is this considered good form?

IMO it is never a good habit to make functions that take an array of Objects and then you implement a method that interprets the params as it wants and does what it wants. I think it is strongly against the whole idea of interface.

It is always a better idea to declare methods that "tell" what they are doing and then just add a new method declaration or refactor your interface and already implemented methods when there is a need for that.

If you choose to pass "a list of objects" anytime you need some flexibility, you are soon a knee-deep in it.

Now this may raise a question that do I have to implement all the methods n this interface? Yes, but if you do not want to you can either define separate interfaces and make your class implement 1 or more of them or/and use extends to make interface hierarchy.

pirho
  • 11,565
  • 12
  • 43
  • 70
  • Thank you for your detailed response. I've decided to limit the scope of my interface as I think the design I had in mind was flawed. Your approach to interfaces is correct which is why I ended up scrapping the very generalized idea I had for the interface. – Yonatan S Aug 12 '20 at 17:05