Visitor Pattern for two arguments

Here is a problem statement: We have interfaces/super classes Student and Teacher

Student has two implementations/sub clasees, ScienceStudent and PhysicalEducationStudent

Teacher has ScienceTeacher and PhysicalEducationTeacher.

We want to implement a method getMeetingPoint(Student s, Teacher t) which returns a place where they meet based on the type of Student and Teacher.

For example, if its a ScienceStudent and ScienceTeacher they meet at Lab if PEStudent and PETeacher they meet on the Ground and if its a ScienceStudent and PETeacher or vice versa, they meet at cafeteria

We can write a naive method, which checks using instanceof . But the problem is, this becomes complex when Teacher or Student gets extended, and tough to maintain. something like this:

public class MeetingPointDecider {

    getMeetingPoint(Student s,Teacher t) {
        if(s instanceof ScienceStudent && t instanceof ScienceTeacher) {
            return "Lab";
        } else if (s instanceof PhysicalEducationStudent && t instanceof PhysicalEducationTeacher) {
            return "GRound";
        }
        .
        .
        .
    }
}

Another option is writing a factory, which accepts a Student and a Teacher and returns something like MeetingPointDecision [Ground or Lab], but the problem persists. Is there any good pattern we can use, where we do not have to modify existing classes (or minimal modification) when a new class is added, Say instanceof ScienceStudent we have ChemistryStudent, PhysicsStudent and ChemistryLab, PhysicsLab. There is also a chance of adding more actions, which differs in implementation based on the Student and Teacher type ( Where Visitor is an option, but not sure how to implement with two deciding classes)

Can someone please suggest a good way to implement this?

Thanks!


I would solve this using a map. The key should identify the teacher + student combination and the value would be the meeting point. for the key I would combine the class names. Here is the solution:

public class MeetingPointDecider
{
    public enum MeetingPoint { Ground, Lab, Cafeteria }
    private static MeetingPoint defaultmp = MeetingPoint.Cafeteria;
    private static Map<String, MeetingPoint> studentTeacherCombinations = new HashMap<>();

    static {
        studentTeacherCombinations.put(getMapKey(ScienceTeacher.class, ScienceStudent.class), MeetingPoint.Lab);
        studentTeacherCombinations.put(getMapKey(PETeacher.class     , PEStudent.class)     , MeetingPoint.Ground);
    }

    public static MeetingPoint getMeetingPoint(Student s,Teacher t)
    {
        String mapKey = getMapKey(t.getClass(), s.getClass()); 
        return studentTeacherCombinations.containsKey(mapKey) ? 
          studentTeacherCombinations.get(mapKey) : defaultmp; 
    }

    private static String getMapKey (Class<? extends Teacher> tCls, Class<? extends Student> sCls)
    {
        return tCls.getName() + "_" + sCls.getName();
    }
}

The logic part is in the static ctor where the map gets populated. It is easy to support future classes.


This is interesting topic because recently Eric Lippert has written article that discuss about this. It is divided in five parts:

  • Part One
  • Part Two
  • Part Three
  • Part Four
  • Part Five
  • The code is written in C# language but I think it should be understandable enough from Java perspective, at least.

    In short, you won't get better result with factory or visitor pattern. Your MeetingPointDecider implementation is already on track. If you still need something that can be less hardcoded or mapped, try sharonbn's solution or similar.

    Or if you need extendable rules, you can try something similar like Decorator pattern:

    public class MeetingPointDecider {
        // the rules, you can add/construct it the way you want
        Iterable<MeetingPointDeciderRule> rules;
        string defaultValue;
        getMeetingPoint(Student s,Teacher t) {
            string result;
            for(MeetingPointDeciderRule rule : rules){
                result = rule.getMeetingPoint(s, t);
                //check whether the result is valid and not null
                //if valid, return result
            }
            //if not valid, return default value
            return defaultValue;
        }
    }
    
    //this can be easily extended
    public abstract class MeetingPointDeciderRule {
        getMeetingPoint(Student s,Teacher t) {
    
        }
    }
    

    Last but not recommended, but if you still need the flexibility, you can try to runtime compile the class and use it as rule engine. But not recommended.

    Note: I am not answering the original question hence the community wiki answer. If this answer format is wrong, I will delete it.


    What if you add a getMeetingKeyPart() method to the interfaces (Student and Teacher) and implement to return specific key parts for each Student and Teacher implementation.

    Eg ScienceStudent returns "ScienceStudent" and ScienceTeacher returns "ScienceTeacher".

    Then you can define a .properties file where meeting points are defined for any desired key combination. Eg

    ScienceStudent-ScienceTeacher=Lab
    PhysicalEducationStudent-PhysicalEducationTeacher=Ground
    ...
    

    If there is no match for the key combination you return "cafeteria"

    链接地址: http://www.djcxy.com/p/26474.html

    上一篇: 如何在Dockerfile RUN中使用管道(ioredirection)?

    下一篇: 访问者模式的两个参数