两个参数的访客模式

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

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

老师有ScienceTeacher和PhysicalEducationTeacher。

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

例如,如果他们是实验室会见的ScienceStudentScienceTeacher ,如果PEStudentPETeacher他们在地面见面,如果是ScienceStudentPETeacher ,反之亦然,他们在自助餐厅见面

我们可以编写一个天真的方法,使用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。 还有可能添加更多操作,这些操作根据学生和教师类型的不同而有所不同(其中访问者是一个选项,但不确定如何使用两个决策类实现)

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

谢谢!

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

 public class MeetingPointDecider { public enum MeetingPoint { Ground, Lab, Cafeteria } private static MeetingPoint defaultmp = MeetingPoint.Cafeteria; private static Map 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 tCls, Class sCls) { return tCls.getName() + "_" + sCls.getName(); } } 

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

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

  • 第一部分
  • 第二部分
  • 第三部分
  • 第四部分
  • 第五部分

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

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

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

 public class MeetingPointDecider { // the rules, you can add/construct it the way you want Iterable 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) { } } 

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

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

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

例如,ScienceStudent返回“ScienceStudent”,ScienceTeacher返回“ScienceTeacher”。

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

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

如果组合键不匹配,则返回“自助餐厅”

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

 public enum Faculty { SCIENCE("Lab", Arrays.asList(ScienceStudent.class, ScienceTeacher.class)), PHYSICAL_EDUCATION("Ground", Arrays.asList(PhysicalEducationStudent.class, PhysicalEducationTeacher.class)), UNKNOWN("Unknown", Collections.>emptyList()); private final List> types = new LinkedList<>(); public final String meetingPlace; Faculty(String meetingPlace, List> 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> 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界面以直接获得“教师”,然后您可以简单地使用它。

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

当然,这并非总是可行,因此是第一种解决方案。

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

 public class MeetingPointDecider implements StudentTeacherVisitor { Decision getMeetingPoint(ScienceStudent s, ScienceTeacher t) { // return result } Decision getMeetingPoint(PEStudent s, PETeacher t) { // return result } // etc. } 

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