两个参数的访客模式
这是一个问题陈述:我们有接口/超级class学生和教师
学生有两个实施/子课程,ScienceStudent和PhysicalEducationStudent
老师有ScienceTeacher和PhysicalEducationTeacher。
我们想要实现一个方法getMeetingPoint(学生,教师t),它根据学生和教师的类型返回他们见面的地方。
例如,如果他们是实验室会见的ScienceStudent和ScienceTeacher ,如果PEStudent和PETeacher他们在地面见面,如果是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。 还有可能添加更多操作,这些操作根据学生和教师类型的不同而有所不同(其中访问者是一个选项,但不确定如何使用两个决策类实现)
有人可以建议一个实现这个的好方法吗?
谢谢!
我会用地图解决这个问题。 关键应该识别教师+学生组合,价值将是会面点。 对于键,我会结合类名。 这是解决方案:
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 extends Teacher> tCls, Class extends Student> 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";
理想情况下,您可以更改Teacher
和Student
界面以直接获得“教师”,然后您可以简单地使用它。
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. }
当然这可能不是你想要的,因为在调用特定的访问者方法时你需要知道学生和教师的具体类型,所以解决方案在编译时发生。 正如其他人建议您可以使用地图/属性方法。