裝飾模式:裝飾模式是在沒必要改變原類文件和使用繼承的狀況下,動態地擴展一個對象的功能。它是經過建立一個包裝對象,也就是裝飾來包裹真實的對象。
如下狀況使用Decorator模式:javascript
在裝飾模式中的各個角色有:java
// 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有自帶的 裝飾器,能夠用來修飾類和方法
實現一個換衣服系統,一我的能夠穿各類服飾,以必定順序輸出穿捉的服裝
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()
上面的版本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()
上面的版本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()
當系統須要新功能的時候,是向舊的類中添加新的代碼。這些新加的代碼一般裝飾了原有類的核心職責或主要行爲,
好比用服飾裝飾人,但這種作法的問題在於,它們在主類中加入了新的字段,新的方法和新的邏輯,從而增長了主類的複雜度
而這些新加入的東西僅僅是爲了知足一些只在某種特定狀況下才會執行的特殊行爲的須要。
而裝飾模式卻提供了一個很是好的解決方案,它把每一個要裝飾的功能放在單獨的類中,
並讓這個類包裝它所要裝飾的對象,所以,當須要執行特殊行爲時,客戶代碼就能夠在運行時根據須要有選擇地、按順序地使用裝飾功能包裝對象了
接下來咱們使用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()
通常咱們寫表單提交都會想下面那樣先驗證,後提交,
可是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) } }
先把驗證函數單獨寫成插件
如今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設計模式與開發實踐》函數