JavaScriptでDecoratorパターンを使ってみる

Decoratorパターンは元の機能に変更を加えず、装飾(ラップ)することで機能を拡張することのできるデザインパターンです。

例えばガソリンの価格計算を考えてみます。

ガソリンの原価は約50円/リットルです(だとしましょう)。でもそこから石油税,ガソリン税,消費税がかかってきて、最終的には約120円/リットル近くの金額になります。ガソリン税にいたっては”本則税率”に加え、”暫定税率”というのも税としてかかってきます。

今後も税率計算が変わらなければ、そのまま実装してしまえばいいのですが、現実はそうではありません。暫定税率はなくなるかもしれませんし、新たな税率が追加される可能性があります。

このように将来的に機能の拡張が見込まれるものに対して、柔軟に対応できるようにするのがDecoratorパターンです。

デコレータパターンでガソリン価格の計算

では、ガソリン価格の計算をデコレータパターンで実装してみます。

//コンストラクタ
function Oil(price){
  this.price = price | 50; // 原価設定
  this.decoratorList = []; // 実行するデコレータ
}

//価格の計算
Oil.prototype.getPrice = function(){
  let price = this.price;

  this.decoratorList.forEach( name => {
    price = Oil.decorators[name].getPrice(price);
  });

  return price;
}

//デコレータの実装
Oil.prototype.decorate = function(decorator){
  this.decoratorList.push(decorator);
}

//各種税率計算
Oil.decorators = {};

//石油税
Oil.decorators.oilTax = {
  getPrice : function(price){
    return price + 2.8;
  }
}

//ガソリン税(本則税率)
Oil.decorators.mainGasTax = {
  getPrice : function(price){
    return price + 28.7;
  }
}

//ガソリン税(本則税率)
Oil.decorators.temporaryGasTax = {
  getPrice : function(price){
    return price + 25.1;
  }
}

//消費税
Oil.decorators.consumptionTax = {
  getPrice : function(price){
    return Math.round(price * 1.1);
  }
}

Oilオブジェクトを作成し、Oil.decoratorsのプロパティに各種税率計算のデコレータを実装します。

Oil.prototype.getPriceで、各デコレータで定義したgetPriceを呼び出し価格の計算を行います。

実際にどのデコレータを使うか?は以下の使用例のように、使用者が実行時にdecoreteメソッドを呼び出すことで決定できます。

使用例

デコレータパターンを使うことで、実行時には各種税率計算を呼び出すだけでガソリン価格の計算ができるようになります。

let oilPrice = new Oil(50);

//各種税率計算
oilPrice.decorate("oilTax"); //石油税
oilPrice.decorate("mainGasTax"); //ガソリン税(本則税率)
oilPrice.decorate("temporaryGasTax"); //ガソリン税(暫定税率)
oilPrice.decorate("consumptionTax"); //消費税

//最終価格
console.log(oilPrice.getPrice()); // 117

機能を拡張してみる

デコレータパターンのメリットは、機能を柔軟に拡張できることです。

再びガソリンの価格の例になりますが、実は沖縄では別の税率がガソリン価格に追加されています。

  • 「沖縄の復帰に伴う特別措置に関する法律」により7円の減税
  • 「沖縄県石油価格調整税条例」により1.5円の課税

機能の追加となると面倒だなーと思うのですが、デコレータパターンを使っているので、この機能拡張はOil.decorators.okinawaTaxを追加で定義するだけで実現が可能です。

//沖縄の復帰に伴う特別措置に関する法律 と 沖縄県石油価格調整税条例による税率計算
Oil.decorators.okinawaTax = {
  getPrice : function(price){
    return price - 7.0 + 1.5;
  }
}

実行の際も、decoreteメソッドでokinawaTaxを追加するだけで、沖縄のガソリン価格の計算ができるようになります。

let oilPrice = new Oil(50);

//各種税率計算
oilPrice.decorate("oilTax"); //石油税
oilPrice.decorate("mainGasTax"); //ガソリン税(本則税率)
oilPrice.decorate("temporaryGasTax"); //ガソリン税(暫定税率)
oilPrice.decorate("okinawaTax"); //ガソリン税(暫定税率)
oilPrice.decorate("consumptionTax"); //消費税

 

このようにデコレータパターンを使えば、既存の機能への変更は行わず、追加だけで機能の拡張を行うことができます。
デコレータパターンは様々な機能追加を行いたい場合に大きな力を発揮するのです。

Related Posts