进击的观察者模式

栏目: 后端 · 发布时间: 6年前

内容简介:Talk is cheap. Show me the code. (译: 屁话少说, 放码过来)从代码中很容易看得出来, 我们就是想实现一个简单的计费功能. 可现实中, 商品的价格可能并不是一成不变的.

Talk is cheap. Show me the code. (译: 屁话少说, 放码过来)

以下所有代码参见 Design pattern transformation .

// 商品的信息: 价格 & 折扣
const data = {
    price: 100,
    discount: 0.8
}

// 顾客信息: 是否威会员 & 购买数量 & 总消费 & 购买时间戳
const customer = {
    "VIP": true,
    "quantity": 10,
    "total": 0,
}

// 总消费计算方式
total = (info) => {
    if(!info.VIP) {
        info.total = data.price * info.quantity;
    } else {
        info.total = data.price * data.discount * info.quantity;
    }
}

total(customer);
console.log('customer', customer);
// customer { VIP: true, quantity: 10, total: 800 }
复制代码

从代码中很容易看得出来, 我们就是想实现一个简单的计费功能. 可现实中, 商品的价格可能并不是一成不变的.

data.price = 200

价格变动后, 我们需要及时地获取总消费, 那么就必须重新调用下 total 计费.

total(customer);
console.log('customer', customer);
// customer { VIP: true, quantity: 10, total: 1600 }
复制代码

这是一个大数据时代, 任何数据都有价值. 现在, 我们还想要每次购买时的时间点.

const customer = {
    "VIP": true,
    "quantity": 10,
    "total": 0,
+   "timeStamp": 0
}
// 获取购买时间
purchaseTime = (info) => {
    info.timeStamp = Date.now();
}
复制代码

于是, 我们需要执行的函数就多了一个.

total(customer)
purchaseTime(customer)
console.log('customer', customer)
// { VIP: true, quantity: 10, total: 1600, timeStamp: 1542293676297 }
复制代码

如果我们的需求还有很多, 而且不知一个customer呢. 那么, 每次价格变化我们需要执行很多步骤, 每次啊, 麻烦得很.

+    const customer1 = {
+    "VIP": false,
+    "quantity": 8,
+    "total": 0,
+    "timeStamp": 0
+    }

    total(customer)
    purchaseTime(customer)
    func(customer)
    ...
    funcN(customer1)
    total(customer1)
    purchaseTime(customer1)
    func(customer1)
    ...
    funcN(customer)
    ...
    funcN(customerN)

复制代码

现在我们就对上面的代码进行观察者模式改造.

用观察者模式改造

从上面的例子中:chestnut::mahjong:️不难看出, 每次价格变化时, 我们都需要重复调用满足需求的方法. 不妨想想, 如果我们把这些方法存储起来, 等到价格变化时再去统一调用, 岂不是很方便. 那么问题来了, 这和之前所说的观察者模式(从观察者模式说起)有什么区别呢? 在此, 我们试着用观察者模式改造下. 首先观察者模式都是一个套路. 先一个类维护一个列表, 对列表有增删和通知更新功能. 另一个类则是提供了更新接口.

// 观察目标类
class Subject {
  constructor() {
    this.observerList = []
  }
  addObserver(observer) {
    this.observerList.push(observer)
  }
  notify(params) {
    this.observerList.forEach(observer => {
      observer.update(params)
    })
  }
}

// 观察者类
class Observer {
  constructor(fn) {
    this.update = fn
  }
}
复制代码

接着, 把我们想要调用的方法包装一下, 存储起来.

// 将要重复使用的包装一下
observer1 = new Observer(total)
observer2 = new Observer(purchaseTime)

// 存起来
let subject = new Subject()
subject.addObserver(observer1)
subject.addObserver(observer2)
复制代码

每次价格改变时, 只需要通知一下即可.

// 调整商品价格
data.price = 100
subject.notify(customer)
subject.notify(customer1)
复制代码

改造结束. 初看起来, 可能变得繁琐了. 但是, 遇到复杂的情况, 这不失是一个好办法. 接下来, 我们看看结合Objec.defineProperty会有什么惊喜.

与Objec.defineProperty结合

支付宝的花呗都可以自己还钱了 , 我们为什么还要别人管着:smirk:. 大家都知道经过Objec.defineProperty处理的对象, 在设置和获取对象属性的时候, 会自动触发响应set和get方法. 利用这一点, 我们就可以做到生活自理了. 熟悉的配方, 熟悉的味道. 熟悉的套路我们不妨再走一遍.

// 观察目标类
class Dependency {
  constructor() {
    this.watcherList = []
  }
  addObserver(observer) {
    this.watcherList.push(observer)
  }
  notify(params) {
    this.watcherList.forEach(watcher => {
      watcher.update(params)
    })
  }
}

// 观察类
class Watcher {
  constructor(fn) {
    this.update = fn
  }
}
复制代码

我们此行的目的, 是要在data.price 或data.discount改变时, 程序能够自动触发, 得到我们想要的结果. 换句话说, 通知更新的时机是在设置data.price或data.discount的时候.

Object.keys(data).forEach(key => {
    let value = data[key]
    const dep = new Dependency()
    Object.defineProperty(data, key, {
        set(newVal) {
            value = newVal
            dep.notify()
        },
        get() {
            return value
        }
    })
})
复制代码

对象的每个属性都给了一个依赖实例, 管理自己的依赖. 考虑到customer有很多个, 需要通知到位. 另外, 添加依赖和管理依赖, 前者是因, 后者是果. 在管理之前我们需要想好怎么添加依赖. 回头看一看.

// 总消费计算方式
total = (info) => {
    if(!info.VIP) {
        info.total = data.price * info.quantity;
    } else {
        info.total = data.price * data.discount * info.quantity;
    }
}
// 获取购买时间
purchaseTime = (info) => {
    info.timeStamp = Date.now();
}
复制代码

我们发现, total 函数依赖于data.price或data.discount的. 如果我们在获取属性时去添加依赖倒是一个好时机.

class Dependency {
    // 省略
}
+   Dependency.targey = null;

class Watcher {
    constructor(fn, key) {
        this.update = fn
+        this.key = key
+        this.value = this.getter()
    }
+    getter() {
+        Dependency.targey = this;
+        // 出发下面的get()
+        this.value = data[this.key];
+        Dependency.targey = null;
+    }
}

Object.keys(data).forEach(key => {
    let value = data[key]
    const dep = new Dependency()
    Object.defineProperty(data, key, {
        set(newVal) {
            value = newVal
            dep.notify()
        },
        get() {
+            if (Dependency.targey) {
+                dep.addObserver(Dependency.targey)
+            }
            return value
        }
    })
})
复制代码

然而 purchaseTime 方法里并没有data.price或data.discount可以设置. 所以这个方法行不通. 那么, 干脆紧接着依赖实例去添加依赖吧. 同时考虑到多个customer, 我们封装下.

// 与defineProperty结合
function defineReactive(data, watcherList, funcList) {
  Object.keys(data).forEach(key => {
    let value = data[key]
    const dep = new Dependency()
    funcList.forEach(func => {
      dep.addObserver(new Watcher(func))
    })
    Object.defineProperty(data, key, {
      set(newVal) {
        value = newVal
        watcherList.forEach(watcher => {
          dep.notify(watcher)
        })
      },
      get() {
        return value
      }
    })
  })
}

defineReactive(data, [customer, customer1], [total, purchaseTime])
复制代码

大功告成, 价格变动时, 我们就会自动获取到想要的结果了. 我都能自理了, 你花呗为嘛还不能自己还钱呢:unamused:


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Algorithms and Theory of Computation Handbook

Algorithms and Theory of Computation Handbook

Mikhail J. Atallah (Editor) / CRC-Press / 1998-09-30 / USD 94.95

Book Description This comprehensive compendium of algorithms and data structures covers many theoretical issues from a practical perspective. Chapters include information on finite precision issues......一起来看看 《Algorithms and Theory of Computation Handbook》 这本书的介绍吧!

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具