装饰模式
描述
装饰模式: 装饰模式是在不必改变原类文件和使用继承的情况下, 动态地扩展一个对象的功能. 它是通过创建一个包装对象, 也就是装饰来包裹真实的对象.
适用性 - 百科
以下情况使用 Decorator 模式:
需要扩展一个类的功能, 或给一个类添加附加职责.
需要动态的给一个对象添加功能, 这些功能可以再动态的撤销.
需要增加由一些基本功能的排列组合而产生的非常大量的功能, 从而使继承关系变的不现实.
当不能采用生成子类的方法进行扩充时. 一种情况是, 可能有大量独立的扩展, 为支持每一种组合将产生大量的子类, 使得子类数目呈爆炸性增长. 另一种情况可能是因为类定义被隐藏, 或类定义不能用于生成子类.
代码示例
在装饰模式中的各个角色有:
抽象构件 (Component) 角色: 给出一个抽象接口, 以规范准备接收附加责任的对象.
具体构件 (Concrete Component) 角色: 定义一个将要接收附加责任的类.
装饰 (Decorator) 角色: 持有一个构件 (Component) 对象的实例, 并实现一个与抽象构件接口一致的接口.
具体装饰 (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 有自带的装饰器 http://es6.ruanyifeng.com/#docs/decorator , 可以用来修饰类和方法
例子 - 换衣服系统
实现一个换衣服系统, 一个人可以穿各种服饰, 以一定顺序输出穿捉的服装
版本 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 和 finery
- 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 就显示出正确的穿衣服顺序; 需要把功能按正确的顺序进行控制, 下面用装饰模式来实现实现
- 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 自带的装饰器来实现
- 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
- }
- }
- ./form
- 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 设计模式与开发实践》
来源: https://juejin.im/entry/5bc4412c5188255c9b13dfb0