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!
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:
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.
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.In your meeting place decider, you can then get the faculties and compare them.
Ideally, you would be able to alter the
Teacher
andStudent
interfaces to get the ´Faculty´ directly, and then you could simply it.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.
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.
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.
If there is no match for the key combination you return "cafeteria"
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.
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 theMeeter
which can determine the order of comparison.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:
The logic part is in the static ctor where the map gets populated. It is easy to support future classes.