资讯专栏INFORMATION COLUMN

装饰模式-使用装饰器来写表单验证插件

scola666 / 3384人阅读

摘要:装饰模式描述装饰模式装饰模式是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。具体装饰角色负责给构件对象添加上附加的责任。

装饰模式 描述
装饰模式:装饰模式是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。
适用性-百科

以下情况使用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有自带的装饰器,可以用来修饰类和方法
例子 - 换衣服系统
实现一个换衣服系统,一个人可以穿各种服饰,以一定顺序输出穿捉的服装
版本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,
) {
  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://www.ucloud.cn/yun/98372.html

相关文章

  • [译] Angular 的 @Host 装饰器和元素注入器

    摘要:装饰器我们为啥要讨论元素注入器而不是装饰器这是因为会把元素注入器依赖解析过程限制在当前组件视图内。但是一旦使用了装饰器,整个依赖解析过程就会在第一阶段完成后停止解析,也就是说,元素注入器只在组件视图内解析依赖,然后就停止解析工作。 原文链接:A curious case of the @Host decorator and Element Injectors in Angular 我...

    marek 评论0 收藏0
  • 从ES6重新认识JavaScript设计模式: 装饰模式

    摘要:什么是装饰器模式向一个现有的对象添加新的功能,同时又不改变其结构的设计模式被称为装饰器模式,它是作为现有的类的一个包装。中的装饰器模式中有一个的提案,使用一个以开头的函数对中的及其属性方法进行修饰。 1 什么是装饰器模式 showImg(https://segmentfault.com/img/remote/1460000015970102?w=1127&h=563); 向一个现有的对...

    wendux 评论0 收藏0
  • 学学AOP之装饰模式

    摘要:但是,这样做的后果就是,我们会不断的改变本体,就像把凤姐送去做整形手术一样。在中,我们叫做引用装饰。所以,这里引入的装饰模式装饰亲切,熟悉,完美。实例讲解装饰上面那个例子,只能算是装饰模式的一个不起眼的角落。 装饰者,英文名叫decorator. 所谓的装饰,从字面可以很容易的理解出,就是给 土肥圆,化个妆,华丽的转身为白富美,但本体还是土肥圆。 说人话.咳咳~ 在js里面一切都是对...

    nihao 评论0 收藏0
  • JavaScript装饰模式

    摘要:用户名不能为空密码不能为空校验未通过使用优化代码返回的情况直接,不再执行后面的原函数用户名不能为空密码不能为空 本文是《JavaScript设计模式与开发实践》的学习笔记,例子来源于书中,对于设计模式的看法,推荐看看本书作者的建议。 什么是装饰者模式? 给对象动态增加职责的方式成为装饰者模式。 装饰者模式能够在不改变对象自身的基础上,在运行程序期间给对象动态地添加职责。这是一种轻便灵活...

    Taste 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<