The Visitor Pattern

The Visitor Pattern

·

3 min read

The visitor pattern is a kind of design pattern which could be used to extend object's functionalities without introducing the modifications to an existing object structure.

Let's see a simple example. Say we have a class Animal and three classes inherit from this class.

class Animal {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
}

class Dog extends Animal {
    constructor(name, age) {
        super(name, age);
    }
}

class Cat extends Animal {
    constructor(name, age,) {
        super(name, age);
    }
}

class Elephant extends Animal {
    constructor(name, age) {
        super(name, age);
    }
}

Now, we want each class have one method called print to print out current states. We can add method into the classes directly.


class Dog extends Animal {
    constructor(name, age) {
        super(name, age);
    }
    print() {
        console.log(`I'm a dog. Name: ${this.name}. Age: ${this.age}.`);
    }
}

class Cat extends Animal {
    constructor(name, age,) {
        super(name, age);
    }
    print() {
        console.log(`I'm a Cat. Name: ${this.name}. Age: ${this.age}.`);
    }
}

class Elephant extends Animal {
    constructor(name, age) {
        super(name, age);
    }
    print() {
        console.log(`I'm a Elephant. Name: ${this.name}. Age: ${this.age}.`);
    }
}

If we want to add another function for these classes, then we need to change the class definition again. Say this time we add method say.

class Dog extends Animal {
    constructor(name, age) {
        super(name, age);
    }
    print() {
        console.log(`I'm a dog. Name: ${this.name}. Age: ${this.age}.`);
    }
    say() {
        console.log("Wang");
    }
}

class Cat extends Animal {
    constructor(name, age,) {
        super(name, age);
    }
    print() {
        console.log(`I'm a Cat. Name: ${this.name}. Age: ${this.age}.`);
    }
    say() {
        console.log("Mia");
    }
}

class Elephant extends Animal {
    constructor(name, age) {
        super(name, age);
    }
    print() {
        console.log(`I'm a Elephant. Name: ${this.name}. Age: ${this.age}.`);
    }
    say() {
        console.log("Monnn");
    }
}

As you can see, each time we add new function, we will need to change the structure of the class.

If somehow we don't want to change or we can't change the structure, we can choose to use this visitor pattern from the scratch.

Let's add an accept method for each classes.


class Dog extends Animal {
    constructor(name, age) {
        super(name, age);
    }
    accept(visitor) {
        return visitor.visitDog(this);
    }
}

class Cat extends Animal {
    constructor(name, age,) {
        super(name, age);
    }
    accept(visitor) {
        return visitor.visitCat(this);
    }
}

class Elephant extends Animal {
    constructor(name, age) {
        super(name, age);
    }
    accept(visitor) {
        return visitor.visitElephant(this);
    }
}

As you can see, the accept method will be passed a visitor argument. Then inside the method, we call this visitor's method and pass current object to this visitor.

In this way, we could implement all new functionalities in this visitor class. Let's try this print function first.

class PrintVisitor {
    visitDog(dog) {
        console.log(`I'm a dog. Name: ${dog.name}. Age: ${dog.age}.`);
    }
    visitCat(cat) {
        console.log(`I'm a Cat. Name: ${cat.name}. Age: ${cat.age}.`);
    }
    visitElephant(elephant) {
        console.log(`I'm a Elephant. Name: ${elephant.name}. Age: ${elephant.age}.`);
    }
}

Now we can pass this visitor to the accept method.

const e = new Elephant("ele", 23);
const printVisitor = new PrintVisitor();
e.accept(printVisitor);

Great. Now we complete this function.

The same logic, we can write a say visitor to add the say function.

class SayVisitor {
    visitDog(dog) {
        console.log("Wang");
    }
    visitCat(cat) {
        console.log("Mia");
    }
    visitElephant(elephant) {
        console.log("Monnn");
    }
}

const sayVisitor = new SayVisitor();
e.accept(sayVisitor);

That's all.