Monday, 2 May 2011

Using the Visitor Pattern

The visitor pattern is a method of separating an algorithm from it’s data with the result that you can add new algorithms easily without violating the open/closed principal. It should therefore be used when you know that you’ll need to add new functionality to your codebase as time goes on. A real world use for this type of pattern would be in the arena of SOA, where you typically have to create XML messages from a global data set. Using this pattern, you can add the code to create new XML messages without modifying (or breaking) your existing code.

A full description of this pattern is available on wikipedia

To demonstrate the visitor pattern, I’m going to use the usual employee details scenario that I’ve used before in this blog. In this example, imagine that the EmployeeBean is populated by a call to the database using some DAO or other and that we may have a whole collection of these beans. In this scenario we need to create a report to print the details and we also need to view them using a web browser. This leads us to two related classes: TextFormatter and HtmlFormatter.


The diagram above shows that we can link these two formatting classes together using a Visitor interface, which is in turn linked to the EmployeeBean using its accept(...) method.

public interface Visitor {

 
/**
   * This is the visit method, used by the implementing class to format our output
   */
 
public void visit(EmployeeBean data);

 
/**
   * This is our method that returns the formated data for printing: see the Single
   * Responsibility Principal, the formatter doesn't need to create the output.
   */
 
public String getResult();
}

Having defined the visitor interface, we can now create the formatting classes:

public class TextFormatter implements Visitor {

 
/** This is the formatted output of the visitor */
 
private volatile String formattedText;

 
/**
   *
@see visitor.algorithm.Visitor#visit(visitor.data.EmployeeBean)
   */
 
@Override
 
public void visit(EmployeeBean data) {
   
StringBuilder sb = new StringBuilder("Employee Name: ");
    sb.append
(data.getFirstName());
    sb.append
(" ");
    sb.append
(data.getSurname());
    sb.append
("\nEmployee Number: ");
    sb.append
(data.getEmployeeNumber());
    sb.append
("\nPhone Number: ");
    sb.append
(data.getPhoneNumber());
    sb.append
("\nDepartment: ");
    sb.append
(data.getDepartment());
    sb.append
("\n");

    formattedText = sb.toString
();
 
}

 
/**
   *
@see visitor.algorithm.Visitor#getResult()
   */
 
@Override
 
public String getResult() {
   
return formattedText;
 
}
}

public class HtmlFormatter implements Visitor {

 
/** This is the formatted output of the visitor */
 
private volatile String html;
 
/** This is the HTML template for the output */
 
private String htmlTemplate;

 
public HtmlFormatter(String htmlTemplateFileName) {
   
this.htmlTemplate = ReadWriteTextFile.getContents(new File(htmlTemplateFileName));
 
}

 
/**
   *
@see visitor.algorithm.Visitor#visit(visitor.data.EmployeeBean)
   */
 
@Override
 
public void visit(EmployeeBean data) {

   
String tmp = htmlTemplate.replace("${FIRSTNAME}", data.getFirstName());
    tmp = tmp.replace
("${SURNAME}", data.getSurname());
    tmp = tmp.replace
("${EMPLOYEE_NUMBER}", data.getEmployeeNumber());
    tmp = tmp.replace
("${DEPARTMENT}", data.getDepartment());
    tmp = tmp.replace
("${PHONENUMBER}", data.getPhoneNumber());
    html = tmp;
 
}

 
/**
   *
@see visitor.algorithm.Visitor#getResult()
   */
 
@Override
 
public String getResult() {
   
return html;
 
}
}

Notice that I use two totally different ways of formatting the output: the first class uses a simple StringBuilder, whilst the HtmlFormatter uses a template file and replaces ${xxx} style place-holders. Next, we define the EmployeeBean that holds the data and can accept a visitor:

public class EmployeeBean {

 
private final String firstName;

 
private final String surname;

 
private final String employeeNumber;

 
private final String phoneNumber;

 
private final String department;

 
public EmployeeBean(String firstName, String surname, String employeeNumber,
      String phoneNumber, String department
) {

   
this.firstName = firstName;
   
this.surname = surname;
   
this.employeeNumber = employeeNumber;
   
this.phoneNumber = phoneNumber;
   
this.department = department;
 
}

 
/**
   * This is the accept method that connects the data to the algorithm
   */
 
public void accept(Visitor visitor) {
   
visitor.visit(this);
 
}

 
public String getFirstName() {
   
return firstName;
 
}

 
public String getSurname() {
   
return surname;
 
}

 
public String getEmployeeNumber() {
   
return employeeNumber;
 
}

 
public String getPhoneNumber() {
   
return phoneNumber;
 
}

 
public String getDepartment() {
   
return department;
 
}
}

Finally, the sample client code need to run this is:

    EmployeeBean employee = new EmployeeBean("John", "Smith", "342232", "0101 333 3432",
       
"Finance");

    Visitor visitor =
new TextFormatter();
    employee.accept
(visitor);
    System.out.println
(visitor.getResult());

    visitor =
new HtmlFormatter(Constants.PATH_TO_RESOURCES
        +
"visitor/EmployeeTemplate.html");
    employee.accept
(visitor);
    System.out.println
(visitor.getResult());

No comments: