Suggested Pages

Thursday, October 18, 2012

Design Pattern Visitor

In this post I will talk about a not common, but useful design pattern called Visitor.

Before describing it, I'm going to show a simple scenario where I will point up common errors in order to emphasize the importance of knowing design pattern over technologies as JPA, EJB etc..

Let us suppose to have a complex domain where we will consider only three classes: Solid, Cone and Cube. These classes belong to a complex domain and their interface should not be modified.

Remember: A class should have only one responsability and the fewest functions as possible.

Now a new task has to be done: You have an heterogeneous structure composed by Solid instances, and you have to create a simple algorithm that prints Solid of this structure in a "Plain Text" format and in a "XML format"..

Common Bad Solution - 1



A common solution to resolve the above task is to add two methods to the Solid interface: printPlainText and printXML. These methods are implemented by Cone and Cube at their manner. Solution is shown above.

HeterogeneousStructure.java

..

public class HeterogeneousStructure {

 private List structures;

 public List getStructures() {
  return structures;
 }

 public void setStructures(List structures) {
  this.structures = structures;
 }
 
}

Solid.java

...

public interface Solid {

 String printPlainText();

 String printXML();
   
}



Cone.java

..

public class Cone implements Solid {

 private int radius;

 private int height;

 public int getRadius() {
  return radius;
 }

 public void setRadius(int radius) {
  this.radius = radius;
 }

 public int getHeight() {
  return height;
 }

 public void setHeight(int height) {
  this.height = height;
 }

 
 public String printPlainText() {
  return "(cone(radius=" + radius + ", height=" + height + "))";
 }

 
 public String printXML() {
  return " " + radius + ", " + height + " ";
 }

}


Cube.java
...

public class Cube implements Solid {

 private int edge;

 public int getEdge() {
  return edge;
 }

 public void setEdge(int edge) {
  this.edge = edge;
 }
 
 public String printPlainText() {
  return "(cube(edge=" + edge + "))";
 }
 
 public String printXML() {
  return "" + edge + "";
 }
}



Suppose a new task says: Create a new algorithm that prints in a JSON format . To accomplish that task you have to add a new method to the Solid interface.
So you have to modified the bean Solid for each new algorithm and this causes the domain is coupled with external features, as the print feature asked for this task.

Common Bad Solution - 2



We have just said that the problem of the first solution is that if you have to add a different print-algorithm, a new method has to be added to the Solid interface.
Another solution that has to be avoided it's that shown below. The basic idea is to create a class that acts like a Strategy and has two methods: the former to visit Cube object and the latter to visit Cone object.

PrintAlgorithmImpl.java

public class PrintAlgorithmImpl implements PrintAlgorithm {

 private PrintStrategy printStrategy;

 public String print(HeterogeneousStructure structure) {
  String printResult = "";
  List structures = structure.getStructures();
  for (Solid solid : structures) {
   
   if (solid instanceof Cube) {
    printResult+=printStrategy.visit((Cube) solid);
   }
      else if (solid instanceof Cone) {
       printResult+=printStrategy.visit((Cone) solid);
   }
  }
  return printResult;
 }

 public PrintStrategy getPrintStrategy() {
  return printStrategy;
 }

 public void setPrintStrategy(PrintStrategy printStrategy) {
  this.printStrategy = printStrategy;
 }
}


PrintStrategy.java

public interface PrintStrategy {

 public String visit(Cone cone);

 public String visit(Cube cube);
}


PrintStrategyXML.java

public class PrintStrategyXML implements PrintStrategy {


 public String visit(Cone cone){
  return "" + cone.getRadius() + "" + cone.getHeight() + "";
 }
 
 public String visit(Cube cube){
  return "" + cube.getEdge() + "";
 }
}


PrintStrategyPlainText.java
public class PrintStrategyPlainText implements PrintStrategy {

 public String visit(Cone cone){
  return "(cone(radius=" + cone.getRadius() + ", height=" + cone.getHeight() + "))";
 }
 
 public String visit(Cube cube){
  return "(cube(edge=" + cube.getEdge() + "))";
 }

}



Solid.java

public interface Solid {

}


Cone.java


public class Cone implements Solid {

 private int radius;

 private int height;

 public int getRadius() {
  return radius;
 }

 public void setRadius(int radius) {
  this.radius = radius;
 }

 public int getHeight() {
  return height;
 }

 public void setHeight(int height) {
  this.height = height;
 }

}


Cube.java

public class Cube implements Solid {

 

 private int edge;

 public int getEdge() {
  return edge;
 }

 public void setEdge(int edge) {
  this.edge = edge;
 }

  
}

The problem of this solution is that you have to discriminate among the subclasses of Solid interface and invoke the right method of PrintStrategy interface.

Visitor Pattern



The Design Pattern Visitor is a behavioural pattern that suggests to reverse the call: the model object is called passing the strategy object used to visit it and not the inverse.

Problem


Consider to use Visitor Design Pattern when:
  • You have to provide many and different operations over a structure with elements having different interfaces;
  • The structure is immutable but operations can change at runtime;
  • You don't want to modify the element of the structure because of new operations on the structure.

Solution


The solution provided by the Design Pattern Visitor is to classify objects into two categories: Visitor and Visited objects. Visited objects permit visitor objects to visit them in a perfect open/closed principle style: Software entities should be open for extension, but closed for modification. Visited objects permit to change the Visitor (alter its behaviour) but their code doesn't change.

Components


  • Visitor:declares a method called visit(ConcreateElement); for each ConcreateElement of the structures;
  • ConcreteVisitor : the concreate class that implements Visitor interface and has the logic of the new operation;
  • Element: defines a method called accept(Visitor v);
  • ConcreateElement: the concreate class that implements Element interface;
  • ObjectStructure: an aggregation of elements that implement Element interface;

PrintAlgorithmImpl.java


public class PrintAlgorithmImpl implements PrintAlgorithm {

 private PrintVisitor printVisitor;

 public PrintVisitor getPrintVisitor() {
  return printVisitor;
 }

 public void setPrintVisitor(PrintVisitor printVisitor) {
  this.printVisitor = printVisitor;
 }

 public String print(HeterogeneousStructure structure) {
  String printResult = "";
  List structures = structure.getStructures();
  for (Solid solid : structures) {
   printResult += solid.accept(printVisitor);
  }
  return printResult;
 }

}

PrintVisitor.java

public interface PrintVisitor {

 public String visit(Cone cone);
 
 public String visit(Cube cube);
}

PrintStrategyPlainText.java


public class PrintVisitorPlainText implements PrintVisitor{

 
 public String visit(Cone cone){
  return "(cone(radius=" + cone.getRadius() + ", height=" + cone.getHeight() + "))";
 }
 
 public String visit(Cube cube){
  return "(cube(edge=" + cube.getEdge() + "))";
 }
}

PrintStrategyPlainText.java


public class PrintVisitorXML implements PrintVisitor{

 
 public String visit(Cone cone){
  return "" + cone.getRadius() + "" + cone.getHeight() + "";
 }
 
 public String visit(Cube cube){
  return "" + cube.getEdge() + "";
 }
}


Solid.java

public interface Solid {

 public String accept(PrintVisitor printVisitor);
}


Cone.java

public class Cone implements Solid {

 private int radius;

 private int height;

 public int getRadius() {
  return radius;
 }

 public void setRadius(int radius) {
  this.radius = radius;
 }

 public int getHeight() {
  return height;
 }

 public void setHeight(int height) {
  this.height = height;
 }

 
 public String accept(PrintVisitor printVisitor) {
  return printVisitor.visit(this);
 }

}


Cube.java

public class Cube implements Solid {

 

 private int edge;

 public int getEdge() {
  return edge;
 }

 public void setEdge(int edge) {
  this.edge = edge;
 }

 public String accept(PrintVisitor printVisitor) {
  return printVisitor.visit(this);
 }
 
}



Conclusions


In conclusion the power of this design pattern lies in the care with which you use. The strength of Visitor is the use of the technique called double dispatch. This technique consists of using a double polymorphism: in fact the execution of the method element.accept (visitor: Visitor) depends on the couple (ConcreateElement, ConcreateVisitor).

See also:


No comments :

Post a Comment

Suggested Pages