Visitor

The final pattern in this section is the visitor pattern. The visitor provides a method of decoupling an algorithm from the object structure on which it operates. If we wanted to perform some action over a collection of objects which differ in type and we want to perform a different action depending on the object type, we would typically need to make use of a large if statement.

Let's get right into an example of this in Westeros. An army is made up of a few different classes of fighting person (it is important that we be politically correct as there are many notable female fighters in Westeros). However, each member of the army implements a hypothetical interface called IMemberOfArmy:

interface IMemberOfArmy{
  printName();
}

A simple implementation of this might be the following:

class Knight {
  constructor() {
    this._type = "Westeros.Army.Knight";
  }
  printName() {
    console.log("Knight");
  }
  visit(visitor) {
    visitor.visit(this);
  }
}

Now we have a collection of these different types, we can use an if statement to only call the printName function on the knights:

var collection = [];
collection.push(new Knight());
collection.push(new FootSoldier());
collection.push(new Lord());
collection.push(new Archer());

for (let i = 0; i<collection.length; i++) {
  if (typeof (collection[i]) == 'Knight')
    collection[i].printName();
  else
    console.log("Not a knight");
}

Except, if you run this code, you'll actually find that all we get is the following:

Not a knight
Not a knight
Not a knight
Not a knight

This is because, despite an object being a knight, it is still an object and typeof will return object in all cases.

An alternative approach is to use instanceof instead of typeof:

var collection = [];
collection.push(new Knight());
collection.push(new FootSoldier());
collection.push(new Lord());
collection.push(new Archer());

for (var i = 0; i < collection.length; i++) {
  if (collection[i] instanceof Knight)
    collection[i].printName();
  else
    console.log("No match");
}

The instance of approach works great until we run into somebody who makes use of the Object.create syntax:

collection.push(Object.create(Knight));

Despite being a knight this will return false when asked if it is an instance of Knight.

This poses something of a problem for us. The problem is exacerbated by the visitor pattern as it requires that the language supports method overloading. JavaScript does not really support this. There are various hacks which can be used to make JavaScript somewhat aware of overloaded methods but the usual advice is to simply not bother and create methods with different names.

Let's not abandon this pattern just yet, though; it is a useful pattern. What we need is a way to reliably distinguish one type from another. The simplest approach is to just define a variable on the class which denotes its type:

var Knight = (function () {
  function Knight() {
    this._type = "Knight";
  }
  Knight.prototype.printName = function () {
    console.log("Knight");
  };
  return Knight;
})();

Given the new _type variable we can now fake having real method overrides:

var collection = [];
collection.push(new Knight());
collection.push(new FootSoldier());
collection.push(new Lord());
collection.push(new Archer());

for (vari = 0; i<collection.length; i++) {
  if (collection[i]._type == 'Knight')
    collection[i].printName();
  else
    console.log("No match");
}

Given this approach we can now implement a visitor. The first step is to expand our various members of the army to have a generic method on them which takes a visitor and applies it:

var Knight = (function () {
  function Knight() {
    this._type = "Knight";
  }
  Knight.prototype.printName = function () {
    console.log("Knight");
  };
  Knight.prototype.visit = function (visitor) {
    visitor.visit(this);
  };
  return Knight;
})();

Now we need to build a visitor. This code approximates the if statements we had in the preceding code:

varSelectiveNamePrinterVisitor = (function () {
  function SelectiveNamePrinterVisitor() {
  }
  SelectiveNamePrinterVisitor.prototype.Visit = function (memberOfArmy) {
    if (memberOfArmy._type == "Knight") {
      this.VisitKnight(memberOfArmy);
    } else {
      console.log("Not a knight");
    }
  };

  SelectiveNamePrinterVisitor.prototype.VisitKnight = function (memberOfArmy) {
    memberOfArmy.printName();
  };
  return SelectiveNamePrinterVisitor;
})();

This visitor would be used as such:

var collection = [];
collection.push(new Knight());
collection.push(new FootSoldier());
collection.push(new Lord());
collection.push(new Archer());
var visitor = new SelectiveNamePrinterVisitor();
for (vari = 0; i<collection.length; i++) {
  collection[i].visit(visitor);
}

As you can see we've pushed the decisions about the type of the item in the collection down to the visitor. This decouples the items themselves from the visitor as can be seen in the following diagram:

Visitor

If we allow the visitor to make decisions about what methods are called on the visited objects there is a fair bit of trickery required. If we can provide a constant interface for the visited objects then all the visitor needs do is call the interface method. This does, however, move logic from the visitor into the objects that are visited, which is contrary to the idea that the objects shouldn't know they are part of a visitor.

Whether suffering through the trickery is worthwhile is really an exercise for you. Personally I would tend to avoid using the visitor pattern in JavaScript as the requirements to get it working are complicated and non-obvious.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.139.240.244