《消失的设计模式》系列之观察者模式 - V2EX
EclipsePrayer

《消失的设计模式》系列之观察者模式

  •  1
     
  •   EclipsePrayer Feb 16, 2020 4179 views
    This topic created in 2287 days ago, the information mentioned may be changed or developed.

    设计模式是面向对象的有用工具,但是编程语言的发展和多种编程范式混合编程的可能,使很多的模式被语言特性取代,或者被其他编程范式解决。

    要解决的问题

    假如你想创建一个机器人,在发布文章的时候,自动同步到知乎、简书等其他的平台。在这里,我们有 3 个实体,一个是发布文章,在该事件发生的时候,分别通知知乎和简书的实体进行同步。同时为了可扩展性,需要支持可能的其他的平台。为了解决这类问题,我们可以使用观察者模式。

    定义

    在对象之间定义一对多的依赖,当一个对象改变时,依赖它的对象都会收到通知,并自动更新。

    面向对象的方式

    UML

    uml

    如上图所示,

    • Subject 接口定义了主题的行为,在我们的例子中,是发布文章。它持有一系列遵循 Observer 接口的实例。可以通过 registerObserver 进行添加,removeObser 进行移除,最终通过 notifyObservers 通知所有的观察者
    • Observer 接口定义了观察者的行为

    代码

    首先定义 Observer 接口:

    interface Observer { update(newBlog: string) } 

    包含一个 update 方法。

    接下来分别定义两个 Observer

    class ZhihuObserver implements Observer { update(newBlog) { console.log('publishing to zhihu...', newBlog) } } class JianshuObserver implements Observer { update(newBlog) { console.log('publishing to jianshu...', newBlog) } } 

    在收到通知(update 方法被调用)的时候,将新文章的内容打印出来。

    定义 Subject 接口:

    interface Subject { registerObserver(o: Observer) removeObserver(o: Observer) notifyObservers(newBlog: string) } 

    这里由于我们需要告知观察者新文章的内容,notifyObservers 接受了一个 string 类型的参数。

    class BlogWriter implements Subject { private observers: Observer[] = [] registerObserver(o: Observer) { this.observers.push(o) } removeObserver(o: Observer) { this.observers = this.observers.filter(v => v !== o) } notifyObservers(blog: string) { this.observers.forEach(o => { o.update(blog) }) } } 

    BlogWriter 类实现了 Subject 接口:

    • 拥有一个私有的 observers 来存储已注册的 Observer。
    • registerObserver 方法将一个 Observer 存储到 observers 数组中
    • removeObserver 方法将指定的 Observer 移除
    • notifyObservers 方法通知所有的 observers(调用其 update 方法)

    来看运行效果:

    const subject: Subject = new BlogWriter() const zhihu = new ZhihuObserver() subject.registerObserver(zhihu) subject.registerObserver(new JianshuObserver()) subject.notifyObservers('hello') // publishing to zhihu... hello // publishing to jianshu... hello subject.removeObserver(zhihu) subject.notifyObservers('world') // publishing to jianshu... world 

    面向对象完整代码

    戴上函数式的思考帽

    其实问题的本质是将一系列的执行过程存储起来,在特定事件发生的时候,执行这些过程。

    我们来修改 Observer 的定义:

    type Observer = (newBlog: string) => void 

    非常直白,就是一个函数定义。那么对应的 Observer 就可以改成:

    const zhihuObserver: Observer = newBlog => { console.log('publishing to zhihu...', newBlog) } const jianshuObserver: Observer = newBlog => { console.log('publishing to jianshu...', newBlog) } 

    就是两个函数定义而已。此时 BlogWriter 类变成了:

    class BlogWriter implements Subject { private observers: Observer[] = [] registerObserver(o: Observer) { this.observers.push(o) } removeObserver(o: Observer) { this.observers = this.observers.filter(v => v !== o) } notifyObservers(blog: string) { this.observers.forEach(o => { o(blog) }) } } 

    实际上,我们可以更进一步,去掉类的枷锁:

    const createBlogWriter = (): Subject => { let observers: Observer[] = [] return { registerObserver: (o: Observer) => { observers.push(o) }, removeObserver: (o: Observer) => { observers = observers.filter(v => v !== o) }, notifyObservers(blog: string) { observers.forEach(o => o(blog)) }, } } 

    这里我们创建了一个函数,createBlogWriter 该函数的返回值是一个实现了 Subject 接口的对象。代码逻辑和之前面向对象的方式相同,不过这里我们使用了闭包来承担私有变量的作用。来看最终的运行效果:

    const subject = createBlogWriter() subject.registerObserver(zhihuObserver) subject.registerObserver(jianshuObserver) subject.notifyObservers('hello') // publishing to zhihu... hello // publishing to jianshu... hello subject.removeObserver(zhihuObserver) subject.notifyObservers('world') // publishing to jianshu... world 

    我们再来看一遍函数式的代码:

    const createBlogWriter = (): Subject => { let observers: Observer[] = [] return { registerObserver: (o: Observer) => { observers.push(o) }, removeObserver: (o: Observer) => { observers = observers.filter(v => v !== o) }, notifyObservers(blog: string) { observers.forEach(o => o(blog)) }, } } 

    非常简洁。而这里真正的强大之处在于,Observer 只是一个函数类型,任何接收一个类型为 string -> void 的函数都可以作为 Observer

    函数式完整代码

    总结

    本着 do not call me, I will call you! 的理念,观察者模式可以在其他对象发生某些变化的时候得到通知。其本质是存储计算过程,稍后执行,换句话说,就是将一些函数存下来,在适当的时候调用而已,如此简单。而越来越多的语言将函数视为一等对象,所以函数作为参数传入,存储,再执行这种模式会非常简单易用。

    6 replies    2020-02-17 10:10:44 +08:00
    lhx2008
        1
    lhx2008  
       Feb 16, 2020 via Android
    代码看不太懂,不过所谓观察者模式,责任链模式,策略模式,装饰器模式都是差不多的,本质上是在执行端的可插拔的函数,只是有些人把面向对象的那些叫做设计模式,面向过程的叫函数式编程
    bugDev
        2
    bugDev  
       Feb 16, 2020 via iPhone
    写的很好,看着很舒服
    darksword21
        3
    darksword21  
    PRO
       Feb 17, 2020 via iPhone   1
    我还以为会在最下面看到微信公众号
    EclipsePrayer
        4
    EclipsePrayer  
    OP
       Feb 17, 2020
    @darksword21 哈哈,欢迎关注公众号呀~ lambdaIO
    EclipsePrayer
        5
    EclipsePrayer  
    OP
       Feb 17, 2020
    @bugDev 谢谢!
    EclipsePrayer
        6
    EclipsePrayer  
    OP
       Feb 17, 2020
    @lhx2008 “本质上是在执行端的可插拔的函数” 对的!
    代码是 TypeScript,如果有没写明白的地方,还请指出
    About     Help     Advertise     Blog     API     FAQ     Solana     2877 Online   Highest 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 65ms UTC 05:13 PVG 13:13 LAX 22:13 JFK 01:13
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86