-
VisitorModeling/DesignPattern 2020. 2. 29. 19:02
1. Overview
- Visitor pattern allows us to define new operations that can be performed on an object without changing the class definition of the object
- Think of this pattern as an object visitor that visits all nodes in an object structure. Each time our visitor visits a particular object from the object structure, that object calls a specific method on a visitor, passing itself as an argument.
- Each time we need a new operation we create a subclass of a visitor, implement the operation in that class and visit the object structure.
- Objects themselves only implement an accept visit where the visitor is pass as an argument. Objects know about the method in visitor created specifically for it and invoke that method inside the accept method.
2. Description
2.1 Implementation
- We create visitor interface by defining visit method for each class we want to support
- The classes who want functionalities provided by visitor define accept method which accepts a visitor. These methods are defined using the visitor interface as parameter type so that we can pass any class implementing the visitor to these methods
- In the accept method implementation, we'll call a method on visitor which is defined specifically for that class
- Next, we implement the visitor interface in on or more classes. Each implementation provides specific functionality for interested classes. If want another feature we create a new implementation of the visitor.
2.2 Consideration
2.2.1 Implementation
- Visitor can work with objects of classes which do not have a common parent. So having a common interface for those classes is optional. However, the code which passes our visitor to these objects must be aware of these individual classes.
- Often visitors need access to an internal state of objects to carry out their work. So we may have to expose the state using getters/setters
2.2.2 Design
2.3 Pitfalls
- Often visitors need access to object's state. So we end up exposing a lot of state through getter methods, weakening the encapsulation
- Supporting a new class in our visitors requires changes to all visitor implementations
- If the classes themselves change then all visitors have to change as well since they have to work with the changed class
- A little bit confusing to understand and implement
3. Usage
- org.dom4j.Visitor and implementation org.dom4j.VisitorSupport in dom4j library
- java.nio.file.fileVisitor and its implementation SimpleFileVisitor
4. Comparison with Strategy
Visitor Strategy All visitor subclasses provide possibly different functionalities from each other In strategy design pattern each subclass represents a separate algorithm to solve the same problem 5. Example
interface Employee { int getPerformanceRating(); void setPerformanceRating(int rating); Collection<Employee> getDirectReports(); int getEmployeeId(); void accept(Visitor visitor); } interface Visitor { void visit(Programmer programmer); void visit(ProjectLead lead); void visit(Manager manager); void visit(VicePresident vp); } abstract class AbstractEmployee implements Employee { private int performanceRating; private String name; private static int employeeIdCounter = 101; private int employeeId; public AbstractEmployee(String name) { this.name = name; employeeId = employeeIdCounter++; } public String getName() { return name; } @Override public int getPerformanceRating() { return performanceRating; } @Override public void setPerformanceRating(int rating) { performanceRating = rating; } @Override public Collection<Employee> getDirectReports() { return Collections.emptyList(); } @Override public int getEmployeeId() { return employeeId; } } class PerformanceRating { private int id; private int personalRating; private int teamAverageRating; private int finalRating; public PerformanceRating(int id, int personalRating) { this.id = id; this.personalRating = personalRating; } public int getId() { return id; } public int getPersonalRating() { return personalRating; } public int getTeamAverageRating() { return teamAverageRating; } public int getFinalRating() { return finalRating; } public void setTeamAverageRating(int teamAverageRating) { this.teamAverageRating = teamAverageRating; } public void setFinalRating(int finalRating) { this.finalRating = finalRating; } } class PrintVisitor implements Visitor { @Override public void visit(Programmer programmer) { String msg = programmer.getName() + " is a " + programmer.getSkill() + " programmer."; System.out.println(msg); } @Override public void visit(ProjectLead lead) { String msg = lead.getName() + " is a Project Lead with " + lead.getDirectReports().size() + " programmers reporting."; System.out.println(msg); } @Override public void visit(Manager manager) { String msg = manager.getName() + " is a Manager with " + manager.getDirectReports().size() + " leads reporting."; System.out.println(msg); } @Override public void visit(VicePresident vp) { String msg = vp.getName() + " is a Vice President with " + vp.getDirectReports().size() + " managers reporting."; System.out.println(msg); } } class Manager extends AbstractEmployee { private List<Employee> directReports = new ArrayList<>(); public Manager(String name,Employee...employees) { super(name); Arrays.stream(employees).forEach(directReports::add); } @Override public Collection<Employee> getDirectReports() { return directReports; } @Override public void accept(Visitor visitor) { visitor.visit(this); } } class Programmer extends AbstractEmployee { private String skill; public Programmer(String name, String skill) { super(name); this.skill = skill; } public String getSkill() { return skill; } @Override public void accept(Visitor visitor) { visitor.visit(this); } } class ProjectLead extends AbstractEmployee { private List<Employee> directReports = new ArrayList<>(); public ProjectLead(String name, Employee...employees) { super(name); Arrays.stream(employees).forEach(directReports::add); } @Override public Collection<Employee> getDirectReports() { return directReports; } @Override public void accept(Visitor visitor) { visitor.visit(this); } } class VicePresident extends AbstractEmployee{ private List<Employee> directReports = new ArrayList<>(); public VicePresident(String name, Employee...employees) { super(name); Arrays.stream(employees).forEach(directReports::add); } @Override public Collection<Employee> getDirectReports() { return directReports; } @Override public void accept(Visitor visitor) { visitor.visit(this); } } class AppraisalVisitor implements Visitor{ private Ratings ratings = new Ratings(); @SuppressWarnings("serial") public class Ratings extends HashMap<Integer, PerformanceRating>{ public int getFinalRating(int empId) { return get(empId).getFinalRating(); } } @Override public void visit(Programmer programmer) { PerformanceRating finalRating = new PerformanceRating(programmer.getEmployeeId(), programmer.getPerformanceRating()); finalRating.setFinalRating(programmer.getPerformanceRating()); ratings.put(programmer.getEmployeeId(), finalRating); } @Override public void visit(ProjectLead lead) { //25% team & 75% personal PerformanceRating finalRating = new PerformanceRating(lead.getEmployeeId(), lead.getPerformanceRating()); int teamAverage = getTeamAverage(lead); int rating = Math.round(0.75f * lead.getPerformanceRating() + 0.25f*teamAverage); finalRating.setFinalRating(rating); finalRating.setTeamAverageRating(teamAverage); ratings.put(lead.getEmployeeId(), finalRating); } @Override public void visit(Manager manager) { //50% team & 50% personal PerformanceRating finalRating = new PerformanceRating(manager.getEmployeeId(), manager.getPerformanceRating()); int teamAverage = getTeamAverage(manager); int rating = Math.round(0.5f * manager.getPerformanceRating() + 0.5f*teamAverage); finalRating.setFinalRating(rating); finalRating.setTeamAverageRating(teamAverage); ratings.put(manager.getEmployeeId(), finalRating); } @Override public void visit(VicePresident vp) { //75% team & 25% personal PerformanceRating finalRating = new PerformanceRating(vp.getEmployeeId(), vp.getPerformanceRating()); int teamAverage = getTeamAverage(vp); int rating = Math.round(0.25f * vp.getPerformanceRating() + 0.75f*teamAverage); finalRating.setFinalRating(rating); finalRating.setTeamAverageRating(teamAverage); ratings.put(vp.getEmployeeId(), finalRating); } private int getTeamAverage(Employee emp) { return (int)Math.round(emp.getDirectReports().stream().mapToDouble(e->e.getPerformanceRating()).average().getAsDouble()); } public Ratings getFinalRatings() { return ratings; } } public class Client { public static void main(String[] args) { Employee emps = buildOrganization(); Visitor visitor = new PrintVisitor(); visitOrgStructure(emps, visitor); // Visitor visitor1 = new AppraisalVisitor(); // visitOrgStructure(emps, visitor1); } private static Employee buildOrganization() { Programmer p1 = new Programmer("Rachel","C++"); Programmer p2 = new Programmer("Andy","Java"); Programmer p3 = new Programmer("Josh","C#"); Programmer p4 = new Programmer("Bill","C++"); ProjectLead pl1 = new ProjectLead("Tina", p1, p2); ProjectLead pl2 = new ProjectLead("Joey", p3, p4); Manager m1 = new Manager("Chad", pl1); Manager m2 = new Manager("Chad II", pl2); VicePresident vp = new VicePresident("Richard", m1,m2); return vp; } private static void visitOrgStructure(Employee emp, Visitor visitor) { emp.accept(visitor); emp.getDirectReports().forEach(e -> visitOrgStructure(e, visitor)); } }
6. Reference
'Modeling > DesignPattern' 카테고리의 다른 글
Composite (0) 2020.02.29 Chain of responsibility (0) 2020.02.29 Iterator (0) 2020.02.29 Command (0) 2020.02.29 Flyweight (0) 2020.02.28