设计模式之装饰器模式

装饰器模式的定义

装饰器模式(decorator pattern):允许向一个先有的对象增添新的功能,同时又不改变其解构。

装饰器模式的优点

  1. 装饰器模式与继承关系的目的都是要扩展对象的功能,但是装饰器模式可以提供比继承更多的灵活性。装饰器模式允许系统动态决定贴上一个需要的装饰,或者除掉一个不需要的装饰。继承关系是不同,继承关系是静态的,它在系统运行前就决定了

  2. 通过使用不同的具体装饰器以及这些装饰类的排列组合,设计师可以创造出很多不同的行为组合

装饰器模式的缺点

  1. 由于使用装饰器模式,可以比使用继承关系需要较少数目的类。使用较少的类,当然使设计比较易于进行。但是另一方面,由于使用装饰器模式会产生比使用继承关系更多的对象,更多的对象会使得查错变得困难,特别是这些对象看上去都很像。

装饰器模式和适配器模式的区别

  1. 适配器模式的意义是要将一个接口转变成另外一个接口,它的目的是通过改变接口来达到重复使用的目的

  2. 装饰器模式不要改变被装饰对象的接口,而是恰恰要保持原有的接口哦,但是增强原有接口的功能,或者改变原有对象的处理方法而提升性能

示例代码

恩,来一个场景:假设我们要去报名上学,通常学校都是汉语(普通话), 但是我们为了更好的教育,可能会报双语学校,那么我么就可以学会英语和汉语了

实现思路:

  1. 创建学校实例,
  2. 创建适配器,即双语学校
  3. 创建学校实例,调用learn方法
// 装饰器模式
// 装饰只是让原来的功能更强大,并不改变原来的功能
// 1. 学校实例
class School {
  constructor (name) {
    this.name = name
  }
  learn (language) {
    console.log(`learn ${language}`)
  }
}

// 2. 创建适配器(双语学校类)
class DoubleLanguageSchool {
  constructor (name) {
    this.school = new Durk(name)
  }
  learn (language) {
    this.school.learn(language)
    console.log(`learn English`)
  }
}
// 3. 创建实例,调用learn方法
let s = new DoubleLanguageSchool('Leo')
s.learn('chinese')

常见开发场景

  • 开发产品,复用类,任意组合类, 以咖啡店为例,我们在点咖啡的时候,除了最简单的咖啡,可能需要加奶,加糖,加泡沫…相对的价格什么的都会变化,我们这样子就需要使用适配模式了
// 某些情况下装饰模式回优于原型继承模式
// 1. 咖啡类
class Coffee {
  make (water) {
    return `${water}+咖啡`
  }
  cost () {
    return 10
  }
}
// 2. 牛奶咖啡
class MilkCoffee {
  constructor (parent) {
    this.parent = parent
  }
  make (water) {
    return `${this.parent.make(water)}+牛奶`
  }
  cost () {
    return this.parent.cost() + 5
  }
}

// 加糖咖啡
class SugarCoffee {
  constructor(parent) {
    this.parent = parent
  }
  make(water) {
    return `${this.parent.make(water)}+糖`
  }
  cost() {
    return this.parent.cost() + 3
  }
}
// 我们通过一下类, 就可以自由组合
let coffee = new Coffee()
let milkCoffee = new MilkCoffee(coffee)
let sugerCoffee = new SugarCoffee(milkCoffee)

let ret = milkCoffee.make('水')
let cost = milkCoffee.cost()
console.log(ret, cost)

let ret2 = sugerCoffee.make('水')
let cost2 = sugerCoffee.cost()
console.log(ret2, cost2)
  • 处理事件之前和之后处理额外的逻辑

    // AOP 就是在核心函数执行之前或者之后执行额外逻辑,俗称切面编程
    
    /**
     * @desc 定义before方法
     * @param {function} beforeFn
     */
    Function.prototype.before = function (beforeFn) {
      let self = this;
      return function () {
        beforeFn.apply(this, arguments)
        self.apply(this, arguments)
      }
    }
    
    /**
     * @desc 定义after方法
     * @param {function} afterFn
     */
    Function.prototype.after = function (afterFn) {
      let self = this;
      return function () {
        self.apply(this, arguments)
        afterFn.apply(this, arguments)
      }
    } 
    /**
     * @desc 购物
     * @param {number} money 
     * @param {string} product 
     */
    function buy(money, product) {
      console.log(`花${money}${product}`)
    }
    
    buy = buy.before(() => {console.log('银行取钱')})
    buy = buy.after(()=>{console.log('将剩余的钱存银行')})
    // 调用
    buy(1888, '游戏机')
    
  • 使用场景之埋点

    • 服务端脚本

      let Koa = require('koa')
      let app = new Koa()
      const _ = require('koa-route');
      let json = {}
      const res = {
        report: (ctx) => {
          console.log(ctx.query)
          let name = ctx.query.name
          if (json[name]) {
            json[name]++
          } else {
            json[name] = 1
          }
          ctx.body = 'hello'
        },
      
        json: (ctx) => {
          ctx.body = json
        }
      };
      
      app.use(_.get('/report', res.report));
      app.use(_.get('/', res.json));
      app.listen(3000)
      
    • 客户管代码

      <!-- 使用场景之埋点 -->
      <!DOCTYPE html>
      <html lang="en">
      <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>Document</title>
      </head>
      <body>
        <button data-name="watermelon" id="watermelon">西瓜</button>
        <button data-name="apple" id="apple">苹果</button>
        <script>
          let watermelon = document.querySelector('#watermelon')
          let apple = document.querySelector('#apple')
          /**
           * @desc 定义after方法
           * @param {function} afterFn
           */
          Function.prototype.after = function (afterFn) {
            let self = this;
            return function () {
              self.apply(this, arguments)
              afterFn.apply(this, arguments)
            }
          }
      
          
          function click() {
            console.log('你点击了'+ this.dataset.name)
          }
          click = click.after(function () {
            let img = new Image()
            img.src = `http://localhost:3000/report?name=${this.dataset.name}`
          })
          Array.from(document.querySelectorAll('button')).forEach(btn => {
            btn.addEventListener('click', click)
          })
        </script>
      </body>
      </html>
      
  • 使用场景之表单验证

    <!-- 使用场景之埋点 -->
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <meta http-equiv="X-UA-Compatible" content="ie=edge">
      <title>表单效验</title>
    </head>
    <body>
      用户名 <input type="text" id="username">
      密码 <input type="password" id="password">
      <button id="submit">提交</button>
      <script>
        /**
       * @desc 定义before方法
       * @param {function} beforeFn
       */
        Function.prototype.before = function (beforeFn) {
          let self = this;
          return function () {
            // 这里要拿到上一次效验完毕的验证结果
            if (beforeFn.apply(this, arguments)) {
              self.apply(this, arguments)
            }
          }
        }
        function submit(params) {
          console.log('提交表单')
        }
        submit = submit.before(function () {
          
          let username = document.getElementById('username').value
          if (!username) {
            alert('用户名不能为空')
            return false
          }
          return true
        })
        submit = submit.before(function () {
            let username = document.getElementById('username').value
            if (username.length < 4) {
              alert('用户名长度最少为4')
              return false
            }
            return true
          })
        document.getElementById('submit').addEventListener('click', submit)
      </script>
    </body>
    </html>
    
  • 其他使用场景

    • axios的请求发起之前的函数
    • vue的路由调整钩子函数
    • 很多场景,就不一一赘述了.

总结

多学习,有助于睡眠

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值