Brian Risk
Brian Risk

Reputation: 1449

Call a method based on the object type

I have two helper methods:

public String load(URL url) {...}
public String load(File file) {...}

I want to have a method that calls the appropriate helper method depending on what object type it receives:

public void operate(Object object) {...}

I understand that there is a convoluted way of doing it which is:

public void operate(Object object) {
    String data = null;
    if (object.getClass().equals(File.class)) {
        data = load((File) object);
    }
    if (object.getClass().equals(URL.class)) {
        data = load((URL) object);
    }
    // operate on the data....
}

However, this does not seem elegant and was curious if there was a better way..

Upvotes: 7

Views: 9849

Answers (6)

Bogdan Ghita
Bogdan Ghita

Reputation: 21

I just stumbled on the same problem and found a different approach using Reflection:

public void operate(Object object) {
    Method method = this.getClass().getDeclaredMethod("load", object.getClass());
    String data = (String) method.invoke(this, object);
    // [...]
}

Of course, it comes with the burden of handling quite a few exceptions.

Upvotes: 2

Chetan Kinger
Chetan Kinger

Reputation: 15212

However, this does not seem elegant and was curious if there was a better way.

That's right. This violates the Open-Closed principle. A class must be open to extension but closed to modification. You are also correct when you say that you need a generic Object. Here's what you can do :

Create a Loader interface

public interface Loader<T> {
   public String load(T t);
}

Create a loader for loading from File

public class FileLoader implements Loader<File> {
     public String load(File f) { 
            //load from file 
     }    
}

Create a loader for loading from Url

public class UrlLoader implements Loader<Url> {
     public String load(URL r) { 
            //load from url 
     }    
}

Create a class that operates on the data

 class DataOperator<T> {
    Loader<T> loader;
    public SomeClient(Loader<T> loader) {
       this.loader = loader;
    }

    public void operate(T inputSource) {
       String data = loader.load(inputSource);
       //operate on the data
    }

 }

Client code can then use the above API as shown below :

DataOperator<File> fileDataOperator = new DataOperator<>(new FileLoader());
fileDataOperator.operate(new File("somefile.txt"));

DataOperator<URL> urlDataOperator = new DataOperator<>(new UrlLoader());
urlDataOperator.operate(new URL("http://somesite.com")); 

You might be thinking that this is a lot of classes for solving a simple problem. However, this is actually inline with the well known Open-Closed design principle. Notice how you control what technique is used for loading the data by creating an instance of an appropriate class. Another advantage you get is that you can decide which technique to use at runtime by creating a Factory that takes user input and creates the appropriate concrete subclass. This is a simplified version of the Strategy pattern.

Disclaimer : The code samples presented above have not been tested for compilation errors as I don't have Java on this machine.

Upvotes: 8

duffymo
duffymo

Reputation: 308813

You're correct: casting is necessary but inelegant.

Another way to do this if you like GoF Design Patterns is the Visitor pattern aka double dispatch.

A third way is to use Java reflection.

Upvotes: 3

CupawnTae
CupawnTae

Reputation: 14580

A slightly less convoluted way is instanceof, e.g.

if (object instanceof File)) {
    data = load((File) object);
}

However, most of the time, using instanceof is a sign that there's a better structure for what you're trying to achieve, e.g.

public void operate(File file) {
    operate(load(file));
}

public void operate(URL url) {
    operate(load(url));
}

public void operate(String data) {
    // operate on the data....
}

Upvotes: 4

Denis Lukenich
Denis Lukenich

Reputation: 3164

Overload the operate-method as well. The use the data which you received to call a method which accepts an String.

public static void operate(URL url) {
    String data = load(url);
    doOperations(data);
}

public static void operate(File file) {
    String data = load(file);
    doOperations(data);
}

private static void doOperations(String data) {
    //TODO Do something with data
}

Upvotes: 1

FrancescoAzzola
FrancescoAzzola

Reputation: 2654

You can use instanceof and check it and then cast the object and call the method:

if (obj instanceof File) {
   ((File) obj).method();
}
else if (obj instanceof URL) {
   ((URL) obj).method();
}

or the inverse like:

if (obj instanceof File) {
 load((File) obj)
}
else if (obj instanceof URL) {
   load((URL) obj)
}

Upvotes: 3

Related Questions