Definition
A behavioral pattern which lets you define new methods to a class hierarchy without changing the class hierarchy on which it operates
Applicability
1. When the primary class hierarchy is fixed or it is coming from a different vendor and you cannot make any changes to that hierarchy
2. Several different operations have to be performed on all or some of the classes in the hierarchy
Visitor and Composite Pattern normally go hand in hand together. First, we will try to create an example of composite pattern. A composite pattern is a structural pattern which helps to create tree like hierarchial structures. Consider a linux filesystem where every element is considered as a file. We are trying to achieve the same tree structure using
Directory
, File
and Link
elements. All of these classes implement the interface FileSystemElement
import java.util.List; /** * File: FileSystemElement.java * */ public interface FileSystemElement extends Visitable { String getName(); String getPath(); void addElement(FileSystemElement elem); List<FileSystemElement> getChildren(); }
import java.util.ArrayList; import java.util.List; /** * File: Directory.java * * A composite class which can contain other composite or leaf classes */ public class Directory implements FileSystemElement { private List<FileSystemElement> children = new ArrayList<FileSystemElement>(); private String name; private String path; public Directory(String name, String path) { this.name = name; this.path = path; } @Override public String getName() { return name; } @Override public String getPath() { return path; } @Override public void accept(Visitor v) { v.visit(this); } @Override public void addElement(FileSystemElement elem) { children.add(elem); } @Override public List<FileSystemElement> getChildren() { return children; } }
import java.util.List; /** * File: File.java * * A leaf class */ public class File implements FileSystemElement { private String name; private String path; public File(String name, String path) { this.name = name; this.path = path; } @Override public String getName() { return name; } @Override public String getPath() { return path; } @Override public void accept(Visitor v) { v.visit(this); } @Override public void addElement(FileSystemElement elem) { // ignore } @Override public List<FileSystemElement> getChildren() { return null; } }
import java.util.List; /** * File: Link.java * */ public class Link implements FileSystemElement { private String name; private String path; public Link(String name, String path) { this.name = name; this.path = path; } @Override public String getName() { return name; } @Override public String getPath() { return path; } @Override public void accept(Visitor v) { v.visit(this); } @Override public void addElement(FileSystemElement elem) { // ignore } @Override public List<FileSystemElement> getChildren() { return null; } }
A visitor pattern is usually referred to as a way of achieving double dispatch in object oriented programming languages as Java, C++. Visitor provides an easy, maintainable way of adding operations for a family of classes. It has a
Visitor
and Visitable
interface. The visitor class performs the operation for each concrete class and the visitable class has the accept
method to call the appropriate method of the visitor passing a reference of itself to the method. The FileSearchVisitor
example below searches for the all the files with a given name./** * File: Visitor.java * */ public interface Visitor { public void visit(Directory dir); public void visit(File file); public void visit(Link link); }
/** * File: Visitable.java * */ public interface Visitable { void accept(Visitor v); }
import java.util.ArrayList; import java.util.List; /** * File: FileSearchVisitor.java * * A visitor class implementation which visits each FileSystem elements * searching for files matching the entered file name */ public class FileSearchVisitor implements Visitor { List<FileSystemElement> results = new ArrayList<FileSystemElement>(); private String fileName; public FileSearchVisitor(String fileName) { this.fileName = fileName; } public void search(FileSystemElement visitable) { visitable.accept(this); } public void showResults() { if (results.size() > 0) { for (FileSystemElement elem : results) { System.out.println(elem.getName() + " " + elem.getPath()); } } else { System.out.println("No results found"); } } @Override public void visit(Directory dir) { for (FileSystemElement elem : dir.getChildren()) { elem.accept(this); } } @Override public void visit(File file) { if (file.getName().equalsIgnoreCase(fileName)) { results.add(file); } } @Override public void visit(Link link) { if (link.getName().equalsIgnoreCase(fileName)) { results.add(link); } } }
/** * File: MainTest.java * * The driver program */ public class MainTest { private static FileSystemElement root; public static void main(String[] args) { setup(); testVisitor(); } // the file search visitor looks for the file name 'shortcut_program_two' // in the root directory private static void testVisitor() { FileSearchVisitor visitor = new FileSearchVisitor("shortcut_program_two"); visitor.search(root); visitor.showResults(); } private static void setup() { root = new Directory("/", "/"); root.addElement(new File("profile", "/profile")); FileSystemElement home = new Directory("home", "/home"); FileSystemElement userdir = new Directory("pankaj", "/home/pankaj"); home.addElement(userdir); FileSystemElement documents = new Directory("documents", "/home/pankaj/documents"); documents.addElement(new File("bash_profile", "/home/pankaj/documents/bash_profile")); documents.addElement(new File("shortcut_program_two", "/home/pankaj/documents/shortcut_program_two")); userdir.addElement(documents); FileSystemElement downloads = new Directory("downloads", "/home/pankaj/downloads"); downloads.addElement(new File("avengers", "/home/pankaj/downloads/avengers")); userdir.addElement(downloads); FileSystemElement desktop = new Directory("desktop", "/home/pankaj/desktop"); desktop.addElement(new Link("shortcut_program_one", "/home/pankaj/desktop/shortcut_program_one")); desktop.addElement(new Link("shortcut_program_two", "/home/pankaj/desktop/shortcut_program_two")); desktop.addElement(new Link("shortcut_program_three", "/home/pankaj/desktop/shortcut_program_three")); userdir.addElement(desktop); root.addElement(home); } }
Output:
shortcut_program_two /home/pankaj/documents/shortcut_program_two shortcut_program_two /home/pankaj/desktop/shortcut_program_two
No comments :
Post a Comment