Visitor Pattern for two arguments

天大地大妈咪最大 提交于 2019-12-05 04:12:44

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.

Fendy

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

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.

E.g. ScienceStudent returns "ScienceStudent" and ScienceTeacher returns "ScienceTeacher".

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

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

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

Assuming you can't change the interfaces, you can create a Faculty enum and add support to it to derive the faculty from the class type.

public enum Faculty {
    SCIENCE("Lab", Arrays.asList(ScienceStudent.class, ScienceTeacher.class)),
    PHYSICAL_EDUCATION("Ground", Arrays.asList(PhysicalEducationStudent.class, PhysicalEducationTeacher.class)),
    UNKNOWN("Unknown", Collections.<Class<?>>emptyList());

    private final List<Class<?>> types = new LinkedList<>();

    public final String meetingPlace;

    Faculty(String meetingPlace,
            List<Class<?>> types) {
        this.meetingPlace = meetingPlace;
        this.types.addAll(types);
    }

    public static Faculty getFaculty(Class<?> type) {
        Faculty faculty = UNKNOWN;
        final Faculty[] values = values();
        for (int i = 0; faculty == UNKNOWN && i < values.length; i++) {
            for (Iterator<Class<?>> iterator = values[i].types.iterator(); faculty == UNKNOWN && iterator.hasNext(); ) {
                final Class<?> acceptableType = iterator.next();
                faculty = type.isAssignableFrom(acceptableType) ? values[i]
                                                                : UNKNOWN;
            }
        }
        return faculty;
    }
}

In your meeting place decider, you can then get the faculties and compare them.

final Faculty studentFaculty = Faculty.getFaculty(student.getClass());
final Faculty teacherFaculty = Faculty.getFaculty(teacher.getClass());
return studentFaculty == teacherFaculty ? teacherFaculty.meetingPlace 
                                        : "cafeteria";

Ideally, you would be able to alter the Teacher and Student interfaces to get the ´Faculty´ directly, and then you could simply it.

final Faculty studentFaculty = student.getFaculty();
final Faculty teacherFaculty = teacher.getFaculty();
return studentFaculty == teacherFaculty ? teacherFaculty.meetingPlace 
                                        : "cafeteria";

Of course, this is not always possible, hence the first solution.

The visitor pattern for 2 arguments will work the same way as for single argument. You just need to use concrete implementations for the method parameters so that the compiler can pick the correct method based on the invocation context.

public class MeetingPointDecider implements StudentTeacherVisitor {

    Decision getMeetingPoint(ScienceStudent s, ScienceTeacher t) {
        // return result
    }

    Decision getMeetingPoint(PEStudent s, PETeacher t) {
        // return result
    }

    // etc.
}

Of course this may not be what you want since when calling a specific visitor method you need to know the concrete types of Student and Teacher so the resolution happens at compile time. As others suggested you can use a Map/Properties approach.

I would create an interface to model the behavior of anyone who can meet. The interface would be implemented by students, teachers, gymnasts, scientists, etc. The implementors would utilize default behavior from the interface or override it with their own. New implementors could be added at any time.

public static void main(String... args) {
    Meeter scienceTeacher = new ScienceTeacher();
    Meeter scienceStudent = new ScienceStudent();
    Meeter gymTeacher = new GymTeacher();
    Meeter gymStudent = new GymStudent();
    System.out.println("Science Teacher & Science Student meet in the " + scienceTeacher.findMeetingPointWith(scienceStudent));
    System.out.println("Science Teacher & Gym Student meet in the " + scienceTeacher.findMeetingPointWith(gymStudent));
    System.out.println("Gym Teacher & Science Student meet in the " + gymTeacher.findMeetingPointWith(scienceStudent));
    System.out.println("Gym Teacher & Gym Student meet in the " + gymTeacher.findMeetingPointWith(gymStudent));
}

interface Meeter {
    enum MeetingPoint { LAB, GYM, CAFETERIA }
    MeetingPoint preferredMeetingPoint();

    default MeetingPoint findMeetingPointWith(Meeter other) {
        MeetingPoint myPreference = preferredMeetingPoint();
        return myPreference == other.preferredMeetingPoint() ? myPreference : defaultMeetingPoint();
    }
    default MeetingPoint defaultMeetingPoint() {
        return MeetingPoint.CAFETERIA;
    }
}

interface Scientist extends Meeter {
    @Override default MeetingPoint preferredMeetingPoint() {
        return MeetingPoint.LAB;
    }
}

interface Gymnast extends Meeter {
    @Override default MeetingPoint preferredMeetingPoint() {
        return MeetingPoint.GYM;
    }
}

static class ScienceTeacher implements Scientist {}
static class ScienceStudent implements Scientist {}
static class GymTeacher implements Gymnast {}
static class GymStudent implements Gymnast {}

Note the above example is not commutative, i.e. A meets B could produce a different result from B meets A. If this is undesirable, consider adding a priority() method to the Meeter which can determine the order of comparison.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!