デコレーションモードは何ができますか?

6157 ワード

デコレーションモードとは


装飾者モードは、関数やクラスに特性を追加する技術であり、元のオブジェクトを修正しない上で、新しい能力と行為を追加することができます.本質的に関数でもあります(javasciptではクラスも関数の構文糖にすぎません).

私たちはいつそれを手に入れることができますか?


1つのシーンを仮定して、1つの自転車店にはいくつかのモデルの自転車があり、現在、店はユーザーが各自転車にヘッドライト、テールランプ、鈴などの追加の部品を提供することを許可しています.1つまたは複数のアクセサリーを選択するたびに、自転車の価格に影響します.
従来のサブクラスの作成方法と比較すると、現在自転車ベースクラスがあり、可能な選択ごとに新しいクラスを作成することになります.しかし、ユーザーは1つまたはいくつかの任意の部品を選択することができるため、最終的に数十以上のサブクラスを生産する可能性があります.これは明らかに科学的ではありません.しかし,この場合,装飾者モードを用いてこの問題を解決することができる.
自転車の基本は以下の通りです.
class Bicycle {
    //     
    wash () {}
    ride () {}
    getPrice() {
        return 200;
    }
}

まず装飾者モードベースクラスを作成することができます
class BicycleDecotator {
    constructor(bicycle) {
        this.bicycle = bicycle;
    }
    wash () {
        return this.bicycle.wash();
    }
    ride () {
        return this.bicycle.ride();
    }
    getPrice() {
        return this.bicycle.getPrice();
    }
}

このベースクラスは実際には何もしていません.Bicycleインスタンスを受け入れ、対応するメソッドを実装し、メソッドを呼び出して返します.
このベースクラスがあれば、私たちのニーズに合わせて元のBicycleクラスを好きなようにすることができます.たとえば、ヘッドライトを追加したアクセサリーと、テールランプを追加したアクセサリーを作成できます.
class HeadLightDecorator extends BicycleDecorator {
    constructor(bicycle) {
        super(bicycle);
    }
    getPrice() {
        return this.bicycle.getPrice() + 20;
    }
}
class TailLightDecorator extends BicycleDecorator {
    constructor(bicycle) {
        super(bicycle);
    }
    getPrice() {
        return this.bicycle.getPrice() + 20;
    }
}

では、次は自由に組み合わせることができます.
let bicycle = new Bicycle();
console.log(bicycle.getPrice()); // 200
bicycle = new HeadLightDecorator(bicycle); //          
console.log(bicycle.getPrice());  // 220
bicycle = new TailLightDecorator(bicycle); //             
console.log(bicycle.getPrice()); // 240

このように書くメリットは何ですか?10個の部品があると仮定すると、10個の部品装飾器を書くだけで、異なる部品の自転車に任意に組み合わせて価格を計算することができます.サブクラスの実装方式であれば、10個の部品には数百個以上のサブクラスが必要になる可能性があります.
例から、装飾者モードの適用場面を見ることができます.
  • クラスに特性や職責を追加する必要があるが、クラスから子を派遣する解決方法が現実的ではない場合は、装飾者モードを使用する必要があります.
  • 例では、元のBicycleベースクラスを変更していないため、元のコードに副作用を及ぼすことはありません.私たちはただ既存の基礎の上でいくつかの機能を追加しただけです.したがって,オブジェクトに特性を付加し,そのオブジェクトを用いたコードを変更したくない場合には,装飾者モードを採用することができる.

  • 装飾者モードはクラスに適用できるほか,関数にも適用できる(実はこれが高次関数である).たとえば、関数の実行時間を測定したい場合は、このような装飾器を書くことができます.
    function func() {
        console.log('func');
    }
    function timeProfileDecorator(func) {
        return function (...args) {
            const startTime = new Date();
            func.call(this, ...args);
            const elapserdTime = (new Date()).getTime() - startTime.getTime();
            console.log(`      ${elapserdTime}ms`);
        }
    }
    const newFunc = timeProfileDecorator(func);
    console.log(newFunc());

    おもしろいことをする


    装飾者モードが元のコードを修正せずに新しい機能を追加できることを知った以上、面白いことをすることができます.
    クラスのメソッドにパフォーマンス分析の機能を提供することができます.
    class TimeProfileDecorator {
      constructor(component, keys) {
        this.component = component;
        this.timers = {};
        const self = this;
        for (let i in keys) {
          let key = keys[i];
            if (typeof component[key] === 'function') {
              this[key] = function(...args) {
                this.startTimer(key);
                //   this      
                component[key].call(component, ...args);
                this.logTimer(key);
              }
            }
        }
      }
      startTimer(namespace) {
        this.timers[namespace] = new Date();
      }
      logTimer(namespace) {
        const elapserdTime = (new Date()).getTime() - this.timers[namespace].getTime();
        console.log(`      ${elapserdTime}ms`);
      }
    }
    // example
    class Test {
      constructor() {
        this.name = 'cjg';
        this.age = 22;
      }
      sayName() {
        console.log(this.name);
      }
      sayAge() {
        console.log(this.age);
      }
    }
    
    let test1 = new Test();
    test1 = new TimeProfileDecorator(test1, ['sayName', 'sayAge']);
    console.log(test1.sayName());
    console.log(test1.sayAge());

    関数の強化


    スロットル関数orブレ止め関数
    function throttle(func, delay) {
        const self = this;
        let tid;
        return function(...args) {
            if (tid) return;
            tid = setTimeout(() => {
                func.call(self, ...args);
                tid = null;
            }, delay);
        }
    }
    
    function debounce(func, delay) {
        const self = this;
        let tid;
        return function(...args) {
            if (tid) clearTimeout(tid);
            tid = setTimeout(() => {
                func.call(self, ...args);
                tid = null;
            }, delay);
        }
    }

    キャッシュ関数の戻り値
    //       ,                   。
    function memorize(func) {
        const cache = {};
        return function (...args) {
            const key = JSON.stringify(args);
            if (cache[key]) {
              console.log('   ');
              return cache[key];
            }
            const result = func.call(this, ...args);
            cache[key] = result;
            return result;
        };
    }
    
    function fib(num) {
      return num < 2 ? num : fib(num - 1) + fib(num - 2);
    }
    
    const enhanceFib = memorize(fib);
    console.log(enhanceFib(40));
    console.log(enhanceFib(40));
    console.log(enhanceFib(40));
    console.log(enhanceFib(40));

    Reactの上位コンポーネントを構築し、コンポーネントにshallowCompare機能などの追加機能を追加します.
    import React from 'react';
    const { Component } = react;
    
    const ShadowCompareDecorator = (Instance) => class extends Component {
      shouldComponentUpdate(nextProps, nextState) {
        return !shallowCompare(this.props, nextProps) ||
          !shallowCompare(this.state, nextState);
      }
      render() {
        return (
          
        );
      }
    };
    
    export default ShadowCompareDecorator;

    もちろん、react-reduxを使ったことがあるなら、connectも使ったことがあるに違いありません.実はconnectも高次コンポーネントの方法です.それは装飾者モードを通じて、Providerのcontextからグローバルなstateを手に入れ、propsで元のコンポーネントに伝えます.

    まとめ


    装飾者モードを使用すると、既存のクラスや関数に新しい機能を追加でき、既存のコードを変更したり呼び出し方法を変更したりすることはありません.そのため、既存のシステムに副作用はありません.元のシステムが機能しなくなったり、互換性がなくなったりする心配はありません.個人的には、とても使いやすいデザインだと思います.
    良いニュースは、jsの装飾器がes 7の草案に組み込まれていることです.装飾者モードをより優雅に使用することができ、興味があればbabelのpluginsプラグインを追加して事前に体験することができます.チェン一峰先生のこのチュートリアルも分かりやすい.
    参考文献:
    Javascript設計モード