A Developer's Diary

Oct 29, 2012

Visitor Design Pattern

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