Design pattern visitor with java reflection
Scritto da Mottola Michele - Italy - Reggio Emilia   
Sabato 11 Aprile 2015 09:26
Ultimo aggiornamento Sabato 11 Aprile 2015 09:34
AddThis Social Bookmark Button

What is the problem

I have a collection of object of different type and i want add some behaviour without modify existing classes. More specific i want add different behaviour based on object's type.
In our case i have objects of type Ferrari and Maserati and i want modify the property colour of this objects and print some message when i do that (this is the new behaviour). Due to this i want object of Ferrari in red and object of Maserati in blue.

Class diagram

Why use visitor

Without the visitor i could create objects of type ConcreteElement and then modify this classes and recompile, but it it a bad idea. Or i could use if statement, something like: if object is of type Ferrari then do that, if is of type Maserati do that. But every time i add new type of object i need to modify that code.
The visitor avoid the modification of that classes and allow to add different behavioural on each type of object.

How visitor work

Basically it work so: the client loop through all object of the collection of type Element (they are inside the object of type Cars, that is only a wrapper) and, for each it invoke the accept method providing an instance of ConcreteVisitor. That method then invoke the visit method that is a method that provide our new behaviour for that type of ConcreteElement. In our example, that method is responsable to change the colour attribute and to print a message.
It is to note that each ConcreteVisitor is able to define an different algorithm based on the type of Element provided.

Sequence diagram

Why use reflection

In standard version of visitor it's used one visit method for each type of ConcretElement. The drawback of this pattern is the creational dependencies that create this, so when i add new ConcreteElement i must modify the interface of visitor and therefore all ConcreteVisitor. In this version of visitor instead the interface of visitor has only one method that accept an object of type Object. Therefore i don't have creationa dependencies. Now if i add new ConcreteElement i don't need to modify the interface of visitor. The reflection help into identification of the type of object provided and in the invocation of the right visit method. More specific accept method of ConcreteVisitor invoke visit(Object o) method. Here it define invoke() method that call the right visit() method based on the type of object provided.

Java implementation

Visitor.java

package visitor5;

public interface Visitor {

	public abstract void visit(Object o);
}

ConcreteVisitor1.java

package visitor5;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class ConcreteVisitor1 implements Visitor {

	@Override
	public void visit(Object o) {
		
		try {
			
			Method m = getClass().getMethod("visit", o.getClass());
			m.invoke(this, o);
			
			
		} catch (SecurityException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			// TODO Auto-generated catch block
			//e.printStackTrace();
			noMethod(o);
			
		} catch (IllegalArgumentException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}
	public void visit(Ferrari f){
		f.setColour("red");
		
		System.out.println("the colour of object Ferrari is now red");
		
		
	}
	public void noMethod(Object o){
		System.out.println("no method");
	}
	

}

ConcreteVisitor2.java

package visitor5;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class ConcreteVisitor2 implements Visitor {

	@Override
	public void visit(Object o) {
		
		try {
			
			Method m = getClass().getMethod("visit", o.getClass());
			m.invoke(this, o);
			
			
		} catch (SecurityException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			// TODO Auto-generated catch block
			//e.printStackTrace();
			noMethod(o);
		} catch (IllegalArgumentException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

	public void noMethod(Object o){
		System.out.println("no method");
	}
	
	public void visit(Maserati m){
		
		m.setColour("blue");
		System.out.println("the colour of object Maserati is now blue");
		
	}
	public void visit(Ferrari f){
		f.setColour("red2");
		
		System.out.println("the colour of object Ferrari is now red2");
	}
	
}

Car.java

package visitor5;

public interface Car {

	public abstract void accept(Visitor v);
}

Ferrari.java

package visitor5;

public class Ferrari implements Car {

	
	private String colour;


	@Override
	public void accept(Visitor v) {
		// i must to call visit(Object o) so i need a cast of this
		Object o=(Object) this;
		v.visit(o);
		
	}
	public String getColour() {
		return colour;
	}
	public void setColour(String colour) {
		this.colour = colour;
	}
	public Ferrari(String colour) {
		super();
		this.colour = colour;
	}

}

Maserati.java

package visitor5;

public class Maserati implements Car {

	private String colour;
	
	@Override
	public void accept(Visitor v) {

		// i must to call visit(Object o) so i need a cast of this
		Object o=(Object) this;
		v.visit(o);
		
	}

	public Maserati(String colour) {
		super();
		this.colour = colour;
	}

	
	public String getColour() {
		return colour;
	}

	public void setColour(String colour) {
		this.colour = colour;
	}

	
}

Cars.java

package visitor5;

import java.util.ArrayList;
import java.util.List;

public class Cars {

	private List<Car> cars= new ArrayList<Car>();
	public void attach(Car c){
		cars.add(c);
		
	}
	public void Accept(Visitor v){
		
		for(Car c:cars){
			c.accept(v);
		}
	}
	
	
	
}

Client.java

package visitor5;

public class Client {

	public static void main(String[] args) {
		
		
		Cars e = new Cars();
		e.attach((Car) new Ferrari("green"));
		e.attach((Car) new Ferrari("yellow"));
		e.attach((Car) new Ferrari("white"));
		
		e.attach((Car) new Maserati("cyan"));
		e.attach((Car) new Maserati("black"));
		
		
		System.out.println("applied ConcreteVisitor1");
		ConcreteVisitor1 inc = new ConcreteVisitor1();
		
		e.Accept(inc);

		System.out.println("-----------------------");
		
		System.out.println("applied ConcreteVisitor2");
		ConcreteVisitor2 inc2 = new ConcreteVisitor2();
		
		e.Accept(inc2);
		

	}

}

Execution

As you can see the object of type ConcreteElement1 is able to handle only object of type Ferrari, therefore il launch an exeption and print a message 'no method' when accept method invoke the visit method and pass it an object of type Maserati.
Moreover you can note that the visit method on object of type Ferrari has implemented in different way in our two ConcreteVisitor. Indeed one set the color to 'red' and the other to 'red2'.

Output

applied ConcreteVisitor1 
the colour of object Ferrari is now red 
the colour of object Ferrari is now red 
the colour of object Ferrari is now red 
no method 
no method 
------------ 
applied ConcreteVisitor2 
the colour of object Ferrari is now red2 
the colour of object Ferrari is now red2 
the colour of object Ferrari is now red2 
the colour of object Maserati is now blue 
the colour of object Maserati is now blue 
 

Aggiungi commento


Codice di sicurezza
Aggiorna