简体   繁体   中英

Java Interface method supporting variable number of arguments with different types

I would like to build an interface containing a method that can take a variable number of arguments of various types, something like as follows:

interface MyInterface {

   boolean myMethod(Object ...args); <--HERE I WOULD LIKE TO PASS ANY NUMBER OF PARMS OF ANY TYPE

}

In the concrete implementations of the interface, I would like to pass any number of params of any type as follows:

class MyImpl1 implements MyInterface {

   boolean myMethod(Student s, Course d) {
      ...
   }
}

Another implementation could be as follows:

 class MyImpl2 implements MyInterface {

   boolean myMethod(Department d) {
      ...
   }
}

In this regard, I have gone through a few posts here, but not getting any idea regarding how to do this. Could anyone please help here? Thanks.

EDIT:

I have thought of the exact interface structure to be like below:

interface ConditionEvaluator {
     
     boolean evaluate(Object ...args);
     CondtionName getConditionName();
   }

One concrete implementation will look something like this:

class StudentEvaluator implements MyInterface {
  
    boolean evaluate(Student s, List<Course> courses) {
      ...
    }
  
    ConditionType getConditionType() {
       return ConditionType.STUDENT_PASSED;
    }
 }

To use this, I have thought that, I will create an enum map, containing ConditionType as key and concrete implementations as values as follows:

Map<ConditionType, MyInterface> conditionTypeMap;

Then from the caller method, was thinking of deriving appropriate implementation based on appropriate ConditionType .

Usage is somewhat like below,

I will have a factory class as follows:

@Component
public class ConditionEvaluatorFactory {

  private final Map<ConditionType, ConditionEvaluator> conditionEvaluatorMap;

  public ConditionEvaluatorFactory(List<ConditionEvaluator>
                                          conditionEvaluatorList) {
    Map<ConditionTyope, ConditionEvaluator> evaluatorMap =
        new EnumMap<>(ConditionEvaluator.class);
    for (ConditionEvaluator evaluator :
        conditionEvaluatorList) {
      evaluatorMap.put(evaluator.getConditionType(),
          evaluator);
    }
    this.conditionEvaluatorMap = evaluatorMap;
  }

  public ConditionEvaluator getEvaluator(ConditionType conditionType) {
    ConditionEvaluator evaluator = conditionEvaluatorMap
        .get(conditionType);
    if (evaluator == null) {
      throw new Exception("error message");
    }
    return evaluator;
  }
}

Caller class is as follows:

@component
class StudentUtil {

 @Autowired
 private ConditionEvaluatorFactory evaluatorFactory;

 public void callerFunc(Student s, List<Course> courseList) {
    boolean flag = evaluatorFactory.getEvaluator(ConditionType.STUDENT_PASSED).evaluate(s, courseList);
 ...
    
 }

Let's start from an important point: the reason why we have an interface which is implemented by multiple children classes is because all of these classes share the same logic , even if they follow a different implementation.

In your case, it doesn't look you can really have an interface (or at least, you cannot do it without having to accept a variable list of unknown types and cast them to static types at some point).

There really is no ideal solution for your problem, but I tried to make up something anyway.

If all your objective was to have a single point where you simply call an evaluator according to a condition you want to check, something you may do is to declare a class that holds a map of inputs:

public class Inputs {

    private final Map<String, Object> inputs = new HashMap<>();

    public static Inputs builder() {
        return new Inputs();
    }

    public Inputs push(String key, Object value) {
        inputs.put(key, value);
        return this;
    }

    public <T> T get(String key) {
        return (T) inputs.get(key); //<-- Unchecked cast, but somewhere you have to do it unfortunately
    }

}

Then, in the enumeration you had called ConditionType , add an abstract method that accepts an Inputs in parameter and returns a boolean as the result of the evaluation.

Once you do that, you can implement the special treatment inside each specific value of the enum:

public enum ConditionType {
    STUDENT_PASSED {
        @Override
        public boolean evaluate(Inputs inputs) {
            Student student = inputs.get("student");
            List<Course> courses = inputs.get("courses");
            //do your logic with student and courses
            return false;
        }
    },
    DEPARTMENT_OPEN {
        @Override
        public boolean evaluate(Inputs inputs) {
            Department department = inputs.get("department");
            //do your logic with department
            return false;
        }
    };

    public abstract boolean evaluate(Inputs inputs);
}

Like that, inside your utility classes, you will be able to do something like this:

public class EvaluationUtils {

    public void callerFunc(Student student, List<Course> courses) {
        Inputs input = Inputs.builder()
                .push("student", student)
                .push("courses", courses);
        boolean flag = ConditionType.STUDENT_PASSED.evaluate(input);
        //...
    }

    public void callerFunc(Department department) {
        Inputs input = Inputs.builder()
                .push("department", department);
        boolean flag = ConditionType.DEPARTMENT_OPEN.evaluate(input);
        //...
    }

}

The only advantage that I see in the above approach is that it avoids you to make checks and casts in each implementation.

Yet, it's not really different than declaring the interface as you originally thought ( boolean evaluate(Object...) ) and each implementation will get the arguments, check the size, the types and then proceed. But then again, your case simply doesn't sound like a case which can be factorized into an interface or any shared method.

This is what Generics are for. Define your interface thus:

interface MyInterface<T> {

    boolean myMethod(T arg);
}

Then your basic one-argument implementation would be:

class MyImpl2 implements MyInterface<Department> {

    boolean myMethod(Department d) {
        ...
    }
}

And your two-argument implemtation would need a holder class:

class CourseEnrollment {
    Student s; Course d;
}

class MyImpl1 implements MyInterface<CourseEnrollment> {

    boolean myMethod(CourseEnrollment ce) {
        ...
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM