Javascript 设计模式-访问者模式(Vistor Pattern)

685 阅读3分钟

简介:

      (Vistor Pattern)访问者模式属于设计模式中的行为模式。它包含访问者和被访问元素两个主要组成部分,访问元素通常都有着不同属性,不同的访问者可以对它进行不同的访问操作。**访问者模式的主要目的是将数据结构与数据操作相分离。**因此,在我们需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作"污染"这些对象的类,使用访问者模式将这些封装到类中。

       换言之,如果系统的数据结构是比较稳定的,但其操作(算法)是易于变化的,那么使用访问者模式是个不错的选择;如果数据结构是易于变化的,则不适合使用访问者模式。

作用:

       稳定的数据结构和易变的操作耦合问题。

在Javascript上使用访问者模式:

     demo:

var Keyboard = function(){
  this.accept = function(computerPartVisitor){
    computerPartVisitor.visit(this);
  }
}
 
var Monitor = function(){
  this.accept = function(computerPartVisitor){
    computerPartVisitor.visit(this);
  }
}
 
var Mouse = function(){
  this.accept = function(computerPartVisitor){
    computerPartVisitor.visit(this);
  }
}
 
var Computer = function(){
  var parts = [new Keyboard(), new Monitor(), new Mouse()];
 
  this.accept = function(computerPartVisitor){
    computerPartVisitor.visit(this);
    _acceptAll(computerPartVisitor);
  }
 
  var _acceptAll = function(computerPartVisitor){
    parts.map(function(item){
      item.accept(computerPartVisitor)
    });
  }
}
 
var ComputerPartDisplayVisitor = function(){
  this.visit = function(item){
    if(item.constructor == Keyboard){
      console.log("Displaying Keyboard.");
    }else if(item.constructor == Monitor){
      console.log("Displaying Monitor.");
    }else if(item.constructor == Mouse){
      console.log("Displaying Mouse.");
    }else if(item.constructor == Computer){
      console.log("Displaying Computer.");
    }else{
      console.log("Error");
    }
  }
}
 
var computer = new Computer();
computer.accept(new ComputerPartDisplayVisitor());
// Displaying Computer.
// Displaying Keyboard.
// Displaying Monitor.
// Displaying Mouse.
  • ComputerPartDisplayVisitor称为访问者,它为对象结构中的每一个具体元素例如Keyboard,Mouse等声明一个访问操作。当访问Keyboard时就会输出Displaying Keyboard。

  • Keyboard,Monitor,Mouse称为元素,他们包含一个accept方法,用来触发传递进来的访问者

  • Computer称为对象结构,它是一个元素的集合。parts数组用于存放元素对象,以供不同访问者访问。_acceptAll方法用来遍历内部元素。

  • 访问者通过accept访问元素内部,元素内部也可以通过参数调用访问者的visit方法。这种调用机制称为双重分派。

        Javascript里有原生的API可以实现访问者模式,像call 和 apply,就是改变函数执行作用域的方法,这也正是访问者模式用法:

// 访问者模式:数组方法封装
var Visitor = (function() {
    return {
        splice: function() {
            var args = Array.prototype.splice.call(arguments, 1);
            return Array.prototype.splice.apply(arguments[0], args);
        },
        push: function() {
            var len = arguments[0].length || 0;
            var args = this.splice(arguments, 1);
            arguments[0].length = len + arguments.length - 1;
            return Array.prototype.push.apply(arguments[0], args);
        },
        pop: function() {
            return Array.prototype.pop.apply(arguments[0]);
        }
    }
})();

var a = new Object();
Visitor.push(a,1,2,3,4);
Visitor.push(a,4,5,6);
Visitor.pop(a);
Visitor.splice(a,2);

优点:

  1. 扩展性好。能够在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。
  2. 复用性好。可以通过访问者来定义整个对象结构通用的功能,从而提高系统的复用程度。
  3. 灵活性好。访问者模式将数据结构与作用于结构上的操作解耦,使得操作集合可相对自由地演化而不影响系统的数据结构。
  4. 符合单一职责原则。访问者模式把相关的行为封装在一起,构成一个访问者,使每一个访问者的功能都比较单一。

缺点:

  1. 增加新的元素类很困难。在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”。
  2. 破坏封装。访问者模式中具体元素对访问者公布细节,这破坏了对象的封装性。
  3. 违反了依赖倒置原则。访问者模式依赖了具体类,而没有依赖抽象类。

test

//
var Employee = function (name, salary, vacation) {
    var self = this;

    this.accept = function (visitor) {
        visitor.visit(self);
    };

    this.getName = function () {
        return name;
    };

    this.getSalary = function () {
        return salary;
    };

    this.setSalary = function (sal) {
        salary = sal;
    };

    this.getVacation = function () {
        return vacation;
    };

    this.setVacation = function (vac) {
        vacation = vac;
    };
};

var ExtraSalary = function () {
    this.visit = function (emp) {
        emp.setSalary(emp.getSalary() * 1.1);
    };
};

var ExtraVacation = function () {
    this.visit = function (emp) {
        emp.setVacation(emp.getVacation() + 2);
    };
};

function run() {

    var employees = [
        new Employee("John", 10000, 10),
        new Employee("Mary", 20000, 21),
        new Employee("Boss", 250000, 51)
    ];

    var visitorSalary = new ExtraSalary();
    var visitorVacation = new ExtraVacation();

    for (var i = 0, len = employees.length; i < len; i++) {
        var emp = employees[i];

        emp.accept(visitorSalary);
        emp.accept(visitorVacation);
        console.log(emp.getName() + ": $" + emp.getSalary() +
            " and " + emp.getVacation() + " vacation days");
    }
}