两个参数的访客模式

时间:2015-05-26 06:16:26

标签: java design-patterns visitor-pattern code-design

这是一个问题陈述: 我们有接口/超级班学生和老师

学生有两个实施/子课程,ScienceStudent和PhysicalEducationStudent

老师有ScienceTeacher和PhysicalEducationTeacher。

我们希望实现一个方法getMeetingPoint(Student s,Teacher t),它根据学生和教师的类型返回他们见面的地方。

例如, 如果它是 ScienceStudent ScienceTeacher ,他们会在实验室见面 如果 PEStudent PETeacher ,他们会在 Ground 上见面 如果它是 ScienceStudent PETeacher ,反之亦然,他们会在自助餐厅会面

我们可以编写一个天真的方法,使用instanceof进行检查。但问题是,当教师或学生扩展并且难以维护时,这变得复杂。 像这样的东西:

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";
        }
        .
        .
        .
    }
}

另一种选择是写一个工厂,它接受学生和教师并返回类似MeetingPointDecision [Ground或Lab]的内容,但问题仍然存在。 我们可以使用任何好的模式,在添加新类时我们不必修改现有的类(或最小的修改),Say instanceof ScienceStudent我们有ChemistryStudent,PhysicsStudent和ChemistryLab,PhysicsLab。 还有可能添加更多动作,这些动作在实现方面有所不同 学生和教师类型(访客是一个选项,但不确定如何实施两个决定课程)

有人可以建议一个好方法来实现这个吗?

谢谢!

6 个答案:

答案 0 :(得分:2)

我会用地图来解决这个问题。关键应该识别教师+学生组合,价值将是会面点。对于键,我会组合类名。这是解决方案:

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();
    }
}

逻辑部分位于静态ctor中,其中填充了地图。很容易支持未来的课程。

答案 1 :(得分:2)

这是一个有趣的话题,因为最近Eric Lippert撰写的文章讨论了这个问题。它分为五个部分:

代码是用C#语言编写的,但我认为至少从Java的角度来看它应该是可以理解的。

简而言之,您无法通过工厂或访客模式获得更好的结果。您的MeetingPointDecider实施已经开始实施。如果你仍然需要一些可以不那么硬编码或映射的东西,可以试试sharonbn的解决方案或类似方法。

或者,如果您需要可扩展规则,您可以尝试类似Decorator模式的东西:

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) {

    }
}

最后但不推荐,但如果您仍需要灵活性,可以尝试运行时编译该类并将其用作规则引擎。但不推荐。

注意:我没有回答原来的问题,因此社区维基回答。如果此答案格式错误,我将删除它。

答案 2 :(得分:0)

如果您将getMeetingKeyPart()方法添加到接口(学生和教师)并实施以返回每个学生和教师实施的特定关键部分,该怎么办。

E.g。 ScienceStudent返回&#34; ScienceStudent&#34;和ScienceTeacher回归&#34; ScienceTeacher&#34;。

然后,您可以定义.properties文件,其中为任何所需的组合键定义会合点。 E.g。

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

如果组合键不匹配,则返回&#34;自助餐厅&#34;

答案 3 :(得分:0)

假设您无法更改界面,您可以创建Faculty枚举并添加对它的支持,以从类类型派生教师。

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;
    }
}

在你的会场决策中,你可以得到这些院系并进行比较。

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

理想情况下,您可以更改TeacherStudent接口以直接获取'Faculty',然后您就可以了。

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

当然,这并不总是可行的,因此是第一个解决方案。

答案 4 :(得分:0)

2个参数的访问者模式的工作方式与单个参数的工作方式相同。您只需要为方法参数使用具体实现,以便编译器可以根据调用上下文选择正确的方法。

public class MeetingPointDecider implements StudentTeacherVisitor {

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

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

    // etc.
}

当然,这可能不是您想要的,因为在调用特定的访问者方法时,您需要了解学生和教师的具体类型,以便在编译时解决问题。正如其他人建议您可以使用地图/属性方法。

答案 5 :(得分:0)

我将创建一个接口来对任何可以见面的人的行为进行建模。该接口将由学生,教师,体操运动员,科学家等实现。实现者将利用该接口中的默认行为,或使用其自己的行为将其覆盖。可以随时添加新的实现器。

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 {}

请注意,上述示例不是可交换的,即A符合B可能会产生与B符合A不同的结果。如果这是不希望的,请考虑向priority()添加Meeter方法,该方法可以确定比较顺序。