Ruby によるデザインパターン 第5章の Observer を JavaScript で写経

変更に追従する: Observer


Rubyによるデザインパターン

Rubyによるデザインパターン

log, Array.forEach, Array.filter を追加。

function log(str) { (console) ? console.log(str) : alert(str); };
// https://developer.mozilla.org/ja/Core_JavaScript_1.5_Reference/Global_Objects/Array/forEach
if (!Array.prototype.forEach) {
  Array.prototype.forEach = function(fun /*, thisp*/)
  {
    var len = this.length;
    if (typeof fun != "function")
      throw new TypeError();

    var thisp = arguments[1];
    for (var i = 0; i < len; i++)
    {
      if (i in this)
        fun.call(thisp, this[i], i, this);
    }
  };
}
// https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Array/filter
if (!Array.prototype.filter) {
  Array.prototype.filter = function(fun /*, thisp*/)
  {
    var len = this.length >>> 0;
    if (typeof fun != "function")
      throw new TypeError();

    var res = new Array();
    var thisp = arguments[1];
    for (var i = 0; i < len; i++)
    {
      if (i in this)
      {
        var val = this[i]; // in case fun mutates this
        if (fun.call(thisp, val, i, this))
          res.push(val);
      }
    }

    return res;
  };
}

通知を受ける

では、給料の変更を経理部門 (Payroll) に逐次通知し続ける、ばか正直なコードを追加してみましょう。

var Employee = function () { this.initialize.apply(this, arguments) };
var Payroll  = function () {};

Payroll.prototype = {
    update: function(changed_employee) {
        log(changed_employee.name + 'のために小切手を送ります');
        log('彼の給料は今' + changed_employee.salaly + 'です');
    }
}

Employee.prototype = {
    initialize: function(name, title, salaly, payroll) {
        this.name    = name;
        this.title   = title;
        this.salaly  = salaly;
        this.payroll = payroll;
    },
    setSalaly: function(new_salary) {
        this.salaly = new_salary;
        this.payroll.update(this);
    }
}

var payroll = new Payroll();
var fred    = new Employee('Fred', 'Crane Oparator', 30000, payroll);
fred.setSalaly(35000);

経理部門に給料の変更を伝える必要があるため、salary フィールドに対しては、attr_accessor を
使用できないことに注意して下さい。代わりに salary= メソッドを書く必要があります。


なるほど、と思ったけど、JavaScript ではどう書けばいいんだろう?
普通にメソッドとして書いた。public メンバーとか private メンバーとか厳密じゃない><

通知を受けるよりよい方法

このコードの問題は、経理部門への給与の変更通知をハードコーディングしている点です。

変化する事柄(誰が給与の変更というニュースを受け取るか)を
Employee オブジェクトの本質から分離するにはどうすればよいのでしょう?

var Employee = function () { this.initialize.apply(this, arguments) };
var Payroll  = function () {};
var Taxman   = function () {};

Payroll.prototype = {
    update: function(changed_employee) {
        log(changed_employee.name + 'のために小切手を送ります');
        log('彼の給料は今' + changed_employee.salaly + 'です');
    }
}

Taxman.prototype = {
    update: function(changed_employee) {
        log(changed_employee.name + 'に新しい請求書を送ります。');
    }
}

Employee.prototype = {
    initialize: function(name, title, salaly) {
        this.name      = name;
        this.title     = title;
        this.salaly    = function() { return salaly };
        this.observers = [];
    },
    setSalaly: function(new_salary) {
        this.salaly = new_salary;
        this.notifyObservers();
    },
    notifyObservers: function() {
        this.observers.forEach(function(observer) {
            observer.update(this);
        }, this);
    },
    addObservers: function(observer) {
        this.observers.push(observer);
    },
    deleteObserver: function(target) {
        this.observers = this.observers.filter(function(observer) {
            return (observer !== target);
        }, this);
    }
}

var fred    = new Employee('Fred', 'Crane Oparator', 30000);
var payroll = new Payroll();
var taxman  = new Taxman();

fred.addObservers(payroll);
fred.addObservers(taxman);
fred.setSalaly(35000);

GOF は「何らかのオブジェクトが変化した」というニュースの発信者と消費者の間にきれいなインターフェースを
作るこのようなアイディアを、Observer パターンと呼んでいます。 GOF はニュースを持っているクラスを
サブジェクト(Subject、話題となっている事柄) クラスと呼んでいます。


このあとはオブザーバに対する責務を分離しながら、継承、モジュール化、
Ruby の標準モジュールであるObservable モジュールの紹介、そしてまたコードブロック
みたいな流れで進んでいく。とりあえず今日はここまで。