1. 程式人生 > 其它 >設計模式篇(6) 裝飾器

設計模式篇(6) 裝飾器

裝飾器模式

裝飾器模式(Decorator Pattern)允許向一個現有的物件新增新的功能,同時又不改變其結構。這種型別的設計模式屬於結構型模式,它是作為現有的類的一個包裝。

這種模式建立了一個裝飾類,用來包裝原有的類,並在保持類方法簽名完整性的前提下,提供了額外的功能。

我們通過下面的例項來演示裝飾器模式的用法。其中,我們將把一個形狀裝飾上不同的顏色,同時又不改變形狀類。

介紹

意圖:動態地給一個物件新增一些額外的職責。就增加功能來說,裝飾器模式相比生成子類更為靈活。

主要解決:一般的,我們為了擴充套件一個類經常使用繼承方式實現,由於繼承為類引入靜態特徵,並且隨著擴充套件功能的增多,子類會很膨脹。

何時使用:在不想增加很多子類的情況下擴充套件類。

如何解決:將具體功能職責劃分,同時繼承裝飾者模式。

關鍵程式碼: 1、Component 類充當抽象角色,不應該具體實現。 2、修飾類引用和繼承 Component 類,具體擴充套件類重寫父類方法。

應用例項: 1、孫悟空有 72 變,當他變成"廟宇"後,他的根本還是一隻猴子,但是他又有了廟宇的功能。 2、不論一幅畫有沒有畫框都可以掛在牆上,但是通常都是有畫框的,並且實際上是畫框被掛在牆上。在掛在牆上之前,畫可以被蒙上玻璃,裝到框子裡;這時畫、玻璃和畫框形成了一個物體。

優點:裝飾類和被裝飾類可以獨立發展,不會相互耦合,裝飾模式是繼承的一個替代模式,裝飾模式可以動態擴充套件一個實現類的功能。

缺點:多層裝飾比較複雜。

使用場景: 1、擴充套件一個類的功能。 2、動態增加功能,動態撤銷。

注意事項:可代替繼承。

實現

1,繪製形狀,並給形狀加一個顏色裝飾器

abstract class Shape {
    abstract draw():void
}

class Circle extends Shape {
    draw(): void {
        console.log('繪製圓形')
    }
}

class Rectangle extends Shape {
    draw(): void {
        console.log('繪製矩形');
    }
}

abstract class ColorfulShape extends Shape {
    constructor(public shape: Shape) {
        super();
    }
    abstract draw():void
}

class RedColorfulShape extends ColorfulShape {
    draw() {
        this.shape.draw();
        console.log('把邊框塗成紅色');
    }
}

let redColorfulShape = new RedColorfulShape(new Circle());
redColorfulShape.draw();

2,類裝飾器

namespace a {
    interface Animal {
        swings: number;
        fly: Function
    }

    function flyable(swings:number) {
        return function(target: any) {
            console.log(target);
            target.prototype.swings = swings;
            target.prototype.fly = function () {
                console.log('我能飛');
            }
        }
    }


    @flyable(2)
    class Animal {
        constructor() { }
    }

    let animal: Animal = new Animal();
    console.log(animal.swings);
    animal.fly();
}

namespace b {
    interface Person {
        protoName: string
    }
    //例項屬性上的target就是類的原型物件 ,key是屬性的名字
    function instancePropertyDecorator(target:any,key:any) {
        target.protoName = '我是類的原型上的屬性';
        console.log('instancePropertyDecorator', target, key)
    }
    //例項方法上的target就是類的原型物件 ,key是屬性的名字
    function instanceMethodDecorator(target:any,key:any) {
        console.log('classPropertyDecorator', target, key);
    }

    //靜態屬性時候target指的是類的建構函式,key方法名 descriptor屬性描述符
    function classPropertyDecorator(target:any,key:any) {
        console.log('classPropertyDecorator',target,key);
    }
    //靜態方法時候target指的是類的建構函式,key方法名 descriptor屬性描述符
    function classMethodDecorator(target:any,key:any) {
        console.log('classMethodDecorator',target,key);
    }


    class Person {
        @instancePropertyDecorator
        instanceProperty: string;//例項屬性
        constructor() {
            this.instanceProperty = '';
        }
        @classPropertyDecorator
        static classProperty: string;//類的靜態屬性
        @instanceMethodDecorator
        instanceMethod() {//例項的方法

        }
        @classMethodDecorator
        static classMethod() {//類的靜態方法

        }
    }
}

3,利用裝飾器實現埋點

import React from 'react';
import ReactDom from 'react-dom';

function before(beforeFn) {
    return function (target:any, methodName: any, descriptor: any) {
        let oldMethod = descriptor.value;
        descriptor.value = function () {
            beforeFn.apply(this, arguments);
            return oldMethod.apply(this, arguments);
        }
    }
}

function after(afterFn) {
    return function (target, methodName, descriptor) {
        let oldMethod = descriptor.value;
        descriptor.value = function() {
            let result = oldMethod.apply(this,arguments);
            afterFn.apply(this, arguments);
            return result;
        }
    }
}
//埋點函式
class App extends React.component {
    @before(() => {console.log('before')})
    onClickBefore() {
        console.log('beforeClick');
    }
    @after(() => {console.log('after')})
    onClickAfter() {
        console.log('afterClick'); 
    }
    @after(() => fetch('/api/report'))
    ajaxClick() {
        console.log('ajaxClick');
    }
    render() {
        return {
            <div>
                <button onClick={this.onClickBefore}>beforeClick</button>
                <button onClick={this.onClickAfter}>afterClick</button>
                <button onClick={this.ajaxClick}>ajaxClick</button>
            </div>
        }
    }
}
  1. 裝飾器實現表單驗證
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
      使用者名稱:<input type="text" id="username" /> 密碼:<input
        type="password"
        id="password"
      />
      <button id="sub">提交</button>

    <script>
      Function.prototype.before = function (beforeFn) {
        let thisFn = this;
        return function () {
          let pass = beforeFn();
          if (pass) {
            thisFn.apply(this, arguments);
          }
        };
      };
      function registerFn(event) {
        console.log("提交表單");
      }
      registerFn = registerFn.before(function () {
        let username = document.getElementById("username").value;
        console.log(username);
        if (!username) {
          return alert("使用者名稱不能為空");
        }
        return true;
      });
      registerFn = registerFn.before(function () {
        let passowrd = document.getElementById("password");
        console.log(password.value)
        if (!password.value) {
          return alert("密碼不能為空");
        }
        return true;
      });
      let btn = document.getElementById("sub");
      btn.addEventListener("click", registerFn);
    </script>
  </body>
</html>