3

We have a Java class, WebCenterGrid. This class is full of methods to do things in a grid such as finding a row, finding a cell value, sorting a column. We have several classes that use this class. The classes using it all refer to different grids, but the functionality is the same.

The only thing that differs is how to create the grid. Some classes do a search which populates the grid (search also refreshes). Some do an updateList() to update the grid, etc.

I would like to add a method to WebCenterGrid to refresh the grid. The problem is as I said each method has a different name.

I somehow want to pass into WebCenterGrid the name of a method to call to do the refresh. I have done some searches and found something about lambda which I did not really understand.

I haven't used C++ in a while but there was some way to pass a method into those methods. This class is in Java not C++, but is there some sort of understandable equivalent?

 public class WebCenterGrid {
    ....
    ....
    public void refresh(Method meth) {
           meth();
    }
 }
Soutzikevich
  • 991
  • 3
  • 13
  • 29
Tony
  • 1,127
  • 1
  • 18
  • 29
  • Have a look at `Method#invoke(Object receiver, Object... arguments)` – Lino Jan 17 '19 at 15:07
  • @Tony, I have updated my answer to make it simpler and more understandable. If it solves your problem, please select it as the approved solution. Please consider up-voting on all of the solutions that other members (me included) provided. They are all valid solutions – Soutzikevich Jan 17 '19 at 18:42

3 Answers3

1

Basically, there are two ways.

One is to use reflection, this means: relying on runtime type information, commonly derived from raw strings. Like saying: I have some object of class X, and I want to invoke the method named "doTheFoo()" on that object.

See here for all the glory details.

A slightly better way is to use the MethodHandle class, instead of the "raw" reflection Method class. See here for handles.

But then: reflection is happening at runtime. Your code compiles fine, but if you get any detail wrong, it blows up at runtime.

Thus I suggest looking into lambdas, based on Function, see here.

GhostCat
  • 137,827
  • 25
  • 176
  • 248
  • 1
    Why not a (functional) interface? – Johannes Kuhn Jan 17 '19 at 15:44
  • Does anyone understand the difference between Class and Class>? I see a lot of our developers use T but when I am trying the above it keeps wanting to change to ? – Tony Jan 17 '19 at 16:10
  • @JohannesKuhn That last link is exactly that: a long list of examples with lambdas and FunctionalInterfaces, but I updated that part. – GhostCat Jan 17 '19 at 16:10
  • @Tony Please dont start asking more questions using comments. I am 100% that there are multiple questions on SO that perfectly answer that one! – GhostCat Jan 17 '19 at 16:11
  • 1
    I ended up using relflection. That seems to be best. I pass two more parameters to my function to get grid. An invoking object (usually "this") and a string with the method name. This method must on "this" object. I can get class type from "this". – Tony Jan 18 '19 at 16:27
  • 1
    @Tony **Be careful!** GhostCat correctly noted: *But then: reflection is happening at runtime. Your code compiles fine, but if you get any detail wrong, it blows up at runtime.* Reflection is a bit of a more advanced technique for special cases and programs. I doubt you need to use reflection in your code, which is fairly simple. Also, if you read more about reflection, you'll often see the suggestion that it should be the **last tool** to use to counter a problem. – Soutzikevich Jan 18 '19 at 18:53
  • yes I did run into a method not being found. It must check only the class and not any super classes. People mentioned interfaces. I was thinking about that, having an interface with "refresh" and having it as a parameter, but the one problem is that the refresh() function from different callers would return a different type, sometimes void. – Tony Jan 23 '19 at 13:14
  • @Tony Yeah, well, then you need a more abstract interface that would cover all your potential use cases. That is the thing when building a framework: you really to design your interfaces very carefully, and when you get that part wrong, you basically build roadblocks right there that will keep frustrating you over and over again. – GhostCat Jan 23 '19 at 13:29
1

Instead of having a Method parameter, accept an Interface, and the implementation will define what will be called.

You can use lambdas here as well if you'll define your interface as Functional Interface.

Example:

public class Main {

    public static void main(String[] args) {
        act(new Run());
        act(new Swim());
        // Passing a body of the function you want to execute
        act(() -> System.out.println("walking"));
    }

    public static void act(Action action) {
        action.act();
    }
}

@FunctionalInterface
interface Action {
    void act();
}

class Run implements Action {
    @Override
    public void act() {
        System.out.println("running");
    }
}

class Swim implements Action {
    @Override
    public void act() {
        System.out.println("swimming");
    }
}

Output:

running
swimming
walking

If you have predefined refresh logic, you can create association resolver based on mapping which will help you to define proper service based on some conditions.

public class Main {

    static Map<ActionType, Action> actionResolver = new HashMap<>();

    // Static init is just for brevity sake
    static {
        actionResolver.put(ActionType.RUN, new Run());
        actionResolver.put(ActionType.WALK, new Walk());
        actionResolver.put(ActionType.SWIM, new Swim());
    }

    public static void main(String[] args) {
        act(ActionType.RUN);
        act(ActionType.WALK);
        act(ActionType.SWIM);
    }

    public static void act(ActionType actionType) {
        Action action = actionResolver.get(actionType);
        if (action == null)
            throw new IllegalArgumentException("ActionType was not registered");

        action.act();
    }
}

enum ActionType {
    RUN,
    SWIM,
    WALK
}

Output is the same as above.

J-Alex
  • 6,881
  • 10
  • 46
  • 64
  • This is a great solution, considering that there are not **too many** ways that a grid is created. If there is only a handful of refresh() procedures, then this is good. If there is a larger number, like 15, or 20, I would not use this solution. – Soutzikevich Jan 17 '19 at 16:03
  • give me a bit to digest this and understand it ;-) Thanks – Tony Jan 17 '19 at 16:23
0

Well, since we can't see any of your code, I'll suggest the following solution, that's based on my personal assumption about how your code works.

Please keep in mind that this method is not so scale-able and pretty inefficient if you have 100 different ways of creating grids.

However, if you have (e.g. 3) types of such ways for example, you can use constants!

See below:

 public class WebCenterGrid {
    //Declare constants with meaningful names for grid creation (add more as you like)
    public static final int DEEP_COPY=1, SEARCH=2, REBUILD=3;

    public void makeDeepCopy(){
    //implementation goes here..
    }

    public void searchAndPopulate(){
    //implementation goes here..
    }

    public void rebuildGrid(){
    //implementation goes here..
    }

    public void refresh(int operation) {
        switch(operation) {
        //based on 'operation', call appropriate method!
        case DEEP_COPY: this.makeDeepCopy(); break;
        case SEARCH: this.searchAndPopulate(); break;
        case REBUILD: this.rebuildGrid(); break;
        //you can have a default operation for any parameter that is not
        //in the list of our defined constants(i.e. the number 143)
        default: simpleRefresh(); break;
        }
    }
 }

So what makes the above solution work?

Basically, when you call refresh(int operation) from one of your other classes, you need to pass an int as a parameter. That integer is one of the constants defined at the very top of the class. According to which constant was passed, the switch case will determine which method to call.

EXAMPLE (Let's say that AwesomeGridCreator is a class that when it calls refresh(), in order to update a grid, it has to do a search and then populate the grid (like you mention in your question).

We name an integer (for simplicity) SEARCH_POPULATE and we give it ANY value we want. For example 286.

We can then use that constant from any other class, because we don't care what its value is (in this case 286, but the functionality it provides when calling refresh().

public class WebCenterGrid {
    /*some code here*/
    public static final int SEARCH_POPULATE = 286; //integer value doesn't matter

    public void refresh(int operation) {
        switch(operation) {
        case SEARCH_POPULATE: this.searchAndPopulate(); break;
    }


    /*...some other code here, we don't care..*/
}

Then, at the 'calling' class:

public class AwesomeGridCreator{
  //some code here

  WebCenterGrid wcg = new WebCenterGrid();
  //The parameter that we pass below (2), will make the refresh() method call
  //the method that we defined in our switch cases ('searchAndPopulate()').
  wcg.refresh(wcg.SEARCH_POPULATE);
}
Soutzikevich
  • 991
  • 3
  • 13
  • 29
  • Will "this" refer to the calling class, or to WebCenterGrid? – Tony Jan 18 '19 at 16:25
  • `this` refers to the current instance of the object, where `this` is used. In the example above, since we call `this` from inside the class `WebCenterGrid`, it means that `this` refers to the instance of `WebCenterGrid` that calls it. Let's say you have **two** `WebCenterGrid` instances; `wcg1` and `wcg2`. If you call `wcg1.refresh(wcg1.SEARCH_POPULATE)`, then the `this` keyword will be equivalent to: `wcg1.searchAndPopulate()`. Also, you can check out [this answer](https://stackoverflow.com/a/3728101/8075923) and I will update my answer – Soutzikevich Jan 18 '19 at 18:23