裝飾模式-使用裝飾器來寫表單驗證插件

裝飾模式

描述

裝飾模式:裝飾模式是在沒必要改變原類文件和使用繼承的狀況下,動態地擴展一個對象的功能。它是經過建立一個包裝對象,也就是裝飾來包裹真實的對象。

適用性-百科

如下狀況使用Decorator模式:javascript

  1. 須要擴展一個類的功能,或給一個類添加附加職責。
  2. 須要動態的給一個對象添加功能,這些功能能夠再動態的撤銷。
  3. 須要增長由一些基本功能的排列組合而產生的很是大量的功能,從而使繼承關係變的不現實。
  4. 當不能採用生成子類的方法進行擴充時。一種狀況是,可能有大量獨立的擴展,爲支持每一種組合將產生大量的子類,使得子類數目呈爆炸性增加。另外一種狀況多是由於類定義被隱藏,或類定義不能用於生成子類。

代碼示例

在裝飾模式中的各個角色有:java

  1. 抽象構件(Component)角色:給出一個抽象接口,以規範準備接收附加責任的對象。
  2. 具體構件(Concrete Component)角色:定義一個將要接收附加責任的類。
  3. 裝飾(Decorator)角色:持有一個構件(Component)對象的實例,並實現一個與抽象構件接口一致的接口。
  4. 具體裝飾(Concrete Decorator)角色:負責給構件對象添加上附加的責任。
// Component類 定義一個對象接口,能夠給這些對象動態的添加職責
abstract class Component {
  abstract Operation (): void
}

// ConcreteComponent 類定義一個具體的對象,也能夠給這個對象添加職責
class ConcreteComponent extends Component {
  Operation () {
    console.log('具體的對象操做');
  }
}

// Decorator 裝飾抽象類,繼承了Component 從外類來拓展Component的功能,可是對於Component來講,無需知道Decorator的存在
abstract class Decorator extends Component{
  protected component: Component | null = null
  // 裝載Component
  SetComponent (component: Component) {
    this.component = component
  }
  // 重寫Operation,實際執行的是component的Operation
  Operation () {
    if (this.component !== null) {
      this.component.Operation()
    }
  }
}

// ConcreteDecorator 具體的裝飾對象 起到給Component類添加職責
class ConcreteDecoratorA extends Decorator {
  private addState: string = ''
  Operation () {
    // 先運行裝飾對象的Operation,若是有的話
    super.Operation()
    this.addState = 'new stateA'
    console.log('具體裝飾對象A的操做');
  }
}

class ConcreteDecoratorB extends Decorator {
  Operation () {
    super.Operation()
    this.AddedBehavior()
    console.log('具體裝飾對象b的操做');
  }
  AddedBehavior () {
    console.log('new state B');
  }
}
// 調用
const c = new ConcreteComponent()
const d1 = new ConcreteDecoratorA()
const d2 = new ConcreteDecoratorB()

d1.SetComponent(c) // d1裝飾的是c
d2.SetComponent(d1) // d2裝飾的是d1
d2.Operation() // d2.Operation中先會調用d1的Operation,d1.Operation中先會調用c的Operation

js的裝飾器

js有自帶的 裝飾器,能夠用來修飾類和方法

例子 - 換衣服系統

實現一個換衣服系統,一我的能夠穿各類服飾,以必定順序輸出穿捉的服裝

版本0

class Person0 {
  private name: string;
  constructor (name: string) {
    this.name = name
  }

  wearTShirts () {
    console.log(' T-shirts')
  }

  wearBigTrouser () {
    console.log(' big trouser')
  }

  wearSneakers () {
    console.log('sneakers ')
  }

  wearSuit () {
    console.log('suit')
  }

  wearLeatherShoes () {
    console.log('LeatherShoes')
  }

  show () {
    console.log(this.name)
  }
}
const person0 = new Person0('lujs')
person0.wearBigTrouser()
person0.wearTShirts()
person0.wearSneakers()
person0.wearSuit()
person0.show()

版本1

上面的版本0,每次要添加不一樣的服飾,就須要修改person類,不符合開放-封閉原則,下面會抽離出服飾類,每一個服飾子類都有添加服飾的方法,這樣就解耦了person和fineryes6

class Person1 {
  private name: string;
  constructor (name: string) {
    this.name = name
  }
  show () {
    console.log(this.name)
  }
}

abstract class Finery {
  abstract show (): void
}

class Tshirts extends Finery {
  show () {
    console.log(' T-shirts')
  }
}
class BigTrouser extends Finery {
  show () {
    console.log(' BigTrouser')
  }
}
class Sneakers extends Finery {
  show () {
    console.log(' Sneakers')
  }
}
// 調用
const person1 = new Person1('lujs')
const ts = new Tshirts()
const bt = new BigTrouser()
const sneakers = new Sneakers()
person1.show()
ts.show()
bt.show()
sneakers.show()

版本2

上面的版本1,單獨抽離了服飾類,這樣就能夠隨意添加服飾而不會影響到person了,
可是在上面的調用代碼中須要按順序去調用自定服飾的show,最好就是隻調用一次show就顯示出正確的穿衣服順序;須要把功能按正確的順序進行控制,下面用裝飾模式來實現實現ajax

class Person2 {
  private name: string = ''
  setName (name: string) {
    this.name = name
  }

  show () {
    console.log('裝扮', this.name);
  }
}
class Finery2 extends Person2 {
  private component: Person2 | null = null
  Decorator (component: Person2) {
    this.component = component
  }
  show () {
    if (this.component !== null) {
      this.component.show()
    }
  }
}

class Tshirts2 extends Finery2 {
  show () {
    super.show()
    console.log('穿tshirt');
  }
}
class BigTrouser2 extends Finery2 {
  show () {
    super.show()
    console.log('穿BigTrouser');
  }
}

const p2 = new Person2()
const t1 = new Tshirts2()
const b1 = new BigTrouser2()
p2.setName('p2')
t1.Decorator(p2)
b1.Decorator(t1)
b1.show()
當系統須要新功能的時候,是向舊的類中添加新的代碼。這些新加的代碼一般裝飾了原有類的核心職責或主要行爲,
好比用服飾裝飾人,但這種作法的問題在於,它們在主類中加入了新的字段,新的方法和新的邏輯,從而增長了主類的複雜度
而這些新加入的東西僅僅是爲了知足一些只在某種特定狀況下才會執行的特殊行爲的須要。
而裝飾模式卻提供了一個很是好的解決方案,它把每一個要裝飾的功能放在單獨的類中,
並讓這個類包裝它所要裝飾的對象,所以,當須要執行特殊行爲時,客戶代碼就能夠在運行時根據須要有選擇地、按順序地使用裝飾功能包裝對象了

版本3

接下來咱們使用js自帶的裝飾器來實現typescript

class Person4 {
  private name: string = ''
  SetName (name: string) {
    this.name = name
  }
  show () {
    console.log('裝備開始', this.name)
  }
}
// 裝飾函數
type fn = () => void
function beforeShow(fns: fn[]) {
  return (target:any, name:any, descriptor:any) => {
    const oldValue = descriptor.value
    descriptor.value = function () {
      const value = oldValue.apply(this, arguments);
      fns.forEach(f:fn => {
        f()
      })
      return value
    }
  }
}
// 使用函數來代替服飾子類
const wearTShirts = () => {
  console.log('wear Tshirts');
}
const wearBigTrouser = () => {
  console.log('wear BigTrouser');
}
class Finery4 extends Person4 {
  private person: Person4 | null = null
  addPerson (person: Person4) {
    this.person = person
  }
  @beforeShow([wearBigTrouser, wearTShirts])
  show () {
    if (this.person !== null) {
      this.person.show()
    }
  }
}
// 須要修改服飾順序的時候,能夠直接修改服飾類的裝飾函數順序,或者生成另外一個類
class Finery5 extends Person4 {
  private person: Person4 | null = null
  addPerson (person: Person4) {
    this.person = person
  }
  @beforeShow([wearTShirts, wearBigTrouser])
  show () {
    if (this.person !== null) {
      this.person.show()
    }
  }
}
const p6 = new Person4()
const f6 = new Finery4()
p6.SetName('lll')
f6.addPerson(p6)
f6.show()

console.log('換一種服飾');
const p7 = new Person4()
const f7 = new Finery4()
p7.SetName('lll')
f7.addPerson(p7)
f7.show()

表單例子

版本0

通常咱們寫表單提交都會想下面那樣先驗證,後提交,
可是submit函數承擔了兩個責任,驗證和提交。
咱們能夠經過裝飾器把驗證的方法剝離出來
const ajax = (url:string, data: any) => {console.log('ajax', url, data)}
class Form {
  state = {
    username: 'lujs',
    password: 'lujs'
  }
  validata = ():boolean => {
    if (this.state.username === '') {
      return false
    }
    if (this.state.password === '') {
      return false
    }
    return true
  }
  submit = () => {
    if (!this.validata()) {
      return
    }
    ajax('url', this.state)
  }
}

版本1

先把驗證函數單獨寫成插件
如今submit函數只有提交數據這個功能,而驗證功能寫成了裝飾器
interface RequestData {
  username: string
  password: string
}
type Vality = (data: RequestData) => boolean
type ValityFail = (data: RequestData) => void
const validata = (data: RequestData):boolean => {
  if (data.username === '') {
    return false
  }
  if (data.password === '') {
    return false
  }
  console.log('驗證經過')
  return true
}
function verify(vality:Vality,  valityFail: ValityFail) {
  return (target:any, name:string, descriptor:any) => {
    const oldValue = descriptor.value
    descriptor.value = function (requestData: RequestData) {
      // 驗證處理
      if (!vality(requestData)) {
        // 驗證失敗處理
        valityFail(requestData)
        return
      }
      // console.log(this, ' == this')
      return oldValue.apply(this, arguments)
    }
    return descriptor
  }
}
class Form1 {
  state = {
    username: '',
    password: 'password'
  }
  @verify(validata, () => console.log('驗證失敗'))
  submit(requestData: RequestData) {
    ajax('url', requestData)
  }
}
console.log('表單驗證例子1開始---')
const f1 = new Form1
f1.submit(f1.state)

f1.state.username = 'lujs'
f1.submit(f1.state)
console.log('表單驗證例子1結束---')

#### 版本2設計模式

把驗證器寫成單獨的插件
/**
 * 一個使用裝飾功能的表單驗證插件
 */
// 先定義一下但願插件的調用方式, 輸入一個數組,內容能夠是字符串或者對象
state = {
  username: 'lujs',
  myEmail: '123@qq.com',
  custom: 'custom'
}
@validate([
  'username', // fail: () =>console.log('username wrong')
  {
    key: 'myEmail',
    method: 'email'
  },
  {
    key: 'myEmail',
    method: (val) => val === 'custom',
    fail: () => alert('fail')
  }
])
submit(requestData: RequestData) {
  ajax('url', requestData)
}

./validator.ts數組

export interface Validator {
  notEmpty (val: string):boolean
  notEmail (val: string):boolean
}
export const validator: Validator = {
  notEmpty: (val: string) => val !== '',
  notEmail: (val: string) => !/^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+(.[a-zA-Z0-9_-])+/.test(val)
}

export interface V {
  key: string
  method: keyof Validator | ((val: any) => boolean)
  fail?: (val: any) => void
}
export function verify(
  vality: Array<V | string>,
) {
  return (target:any, propertyKey:string, descriptor:PropertyDescriptor) => {
    const oldValue = descriptor.value
    descriptor.value = function (requestData: {[p: string]: any}) {
      // 驗證處理
      const flag = vality.every((v) => {
        console.log(typeof v, v, ' == this')
        if (typeof v === 'string') {
          const val = requestData[v]
          // 默認進行empty判斷
          if (!validator.notEmpty(val)) {
            return false
          }
        } else {
          // 對象的狀況
          const val = requestData[v.key]
          console.log(val, ' => val')
          console.log(v, ' => v')
          if (typeof v.method === 'string') {
            if (!validator[v.method](val)) {
              if (v.fail) {
                v.fail.apply(this, requestData)
              }
              return false
            }
          } else {
            console.log(v.method(val), val)
            if (!v.method(val)) {
              if (v.fail) {
                v.fail.apply(this, requestData)
              }
              return false
            }
          }
        }
        return true
      })
      if (!flag) {
        return
      }
      return oldValue.apply(this, arguments)
    }
    return descriptor
  }
}

./formapp

import {verify} from './validator'

const ajax = (url:string, data: any) => {console.log('ajax', url, data)}
class Form2 {
  state = {
    username: 'lujs',
    myEmail: '123@qq.com',
    custom: 'custom'
  }
  @verify([
    'username', // fail: () =>console.log('username wrong')
    {
      key: 'myEmail',
      method: 'notEmail'
    },
    {
      key: 'myEmail',
      method: (val) => val !== 'custom',
      fail: (val) => console.log(val)
    }
  ])
  submit(requestData: {[p: string]: any}) {
    ajax('url', requestData)
  }
}

const f2 = new Form2
f2.submit(f2.state)

例子來自《大話設計模式》《javascript設計模式與開發實踐》函數

相關文章
相關標籤/搜索