面试官经常问的观察者模式如何实现~

大家好,我是若川。持续组织了8个月源码共读活动,感兴趣的可以 点此加我微信ruochuan12 参与,每周大家一起学习200行左右的源码,共同进步。同时极力推荐订阅我写的《学习源码整体架构系列》 包含20余篇源码文章。历史面试系列。另外:目前建有江西|湖南|湖北籍前端群,可加我微信进群。

本文作者有一个设计模式系列:https://pattern.windliang.wang/ 推荐大家收藏保存学习。

代码也写了几年了,设计模式处于看了忘,忘了看的状态,最近对设计模式有了点感觉,索性就再学习总结下吧。

大部分讲设计模式的文章都是使用的 Java、C++ 这样的以类为基础的静态类型语言,作为前端开发者,js 这门基于原型的动态语言,函数成为了一等公民,在实现一些设计模式上稍显不同,甚至简单到不像使用了设计模式,有时候也会产生些困惑。

下面按照「场景」-「设计模式定义」- 「代码实现」-「总」的顺序来总结一下,如有不当之处,欢迎交流讨论。

场景

假设我们在开发一款外卖网站,进入网站的时候,第一步需要去请求后端接口得到用户的常用外卖地址。然后再去请求其他接口、渲染页面。如果什么都不考虑可能会直接这样写:

// getAddress 异步请求
// 页面里有三个模块 A,B,C 需要拿到地址后再进行下一步
// A、B、C 三个模块都是不同人写的,提供了不同的方法供我们调用getAddress().then(res => {const address = res.address;A.update(address)B.next(address)C.change(address)
})

此时页面里多了一个模块 D ,同样需要拿到地址后进行下一步操作,我们只好去翻请求地址的代码把 D 模块的调用补上。

// getAddress 异步请求
// 页面里有三个模块 A,B,C 需要拿到地址后再进行下一步
// A、B、C 三个模块都是不同人写的,提供了不同的方法供我们调用getAddress().then(res => {const address = res.address;A.update(address)B.next(address)C.change(address)D.init(address)
})

可以看到各个模块和获取地址模块耦合严重,A、B、C 模块有变化或者有新增模块,都需要深入到获取地址的代码去修改,一不小心可能就改出问题了。

此时就需要观察者模式了。

设计模式定义

可以看下 维基百科的介绍:

★

The observer pattern is a software design pattern in which an object, named the subject, maintains a list of its dependents, called observers, and notifies them automatically of any state changes, usually by calling one of their methods.

”

很好理解的一个设计模式,有一个 subject 对象,然后有很多 observers 观察者对象,当 subject 对象有变化的时候去通知 observer 对象即可。

再看一下 UML 图和时序图:

ca2dfc7cd0aa67d08fa0beda9913658c.png
image-20220127110751274

每一个观察者都实现了 update 方法,并且调用 Subject 对象的 attach 方法订阅变化。当 Subject 变化时,调用 Observer 的 update 方法去通知观察者。

先用 java 写一个简单的例子:

公众号文章可以看作是 Subject ,会不定期更新。然后每一个用户都是一个 Observer ,订阅公众号,当更新的时候就可以第一时间收到消息。

import java.util.ArrayList;interface Observer {public void update();
}
// 提取 Subject 的公共部分
abstract class Subject {private ArrayList<Observer> list = new ArrayList<Observer>();public void attach(Observer observer){list.add(observer);}public void detach(Observer observer){list.remove(observer);}public void notifyObserver(){for(Observer observer : list){observer.update();}}
}
// 具体的公众号,提供写文章和得到文章
class WindLiang extends Subject {private String post;public void writePost(String p) {post = p;}public String getPost() {return post;}
}// 小明
class XiaoMing implements Observer {private WindLiang subject;XiaoMing(WindLiang sub) {subject = sub;}@Overridepublic void update(){String post = subject.getPost();System.out.println("我收到了" + post + " 并且点了个赞");}
}// 小杨
class XiaoYang implements Observer {private WindLiang subject;XiaoYang(WindLiang sub) {subject = sub;}@Overridepublic void update(){String post = subject.getPost();System.out.println("我收到了" + post + " 并且转发了");}
}// 小刚
class XiaoGang implements Observer {private WindLiang subject;XiaoGang(WindLiang sub) {subject = sub;}@Overridepublic void update(){String post = subject.getPost();System.out.println("我收到了" + post + " 并且收藏");}
}public class Main {public static void main(String[] args) {WindLiang windliang = new WindLiang(); // SubjectXiaoMing xiaoMing = new XiaoMing(windliang);XiaoYang xiaoYang = new XiaoYang(windliang);XiaoGang xiaoGang = new XiaoGang(windliang);// 添加观察者windliang.attach(xiaoMing);windliang.attach(xiaoYang);windliang.attach(xiaoGang);windliang.writePost("新文章-观察者模式,balabala"); // 更新文章windliang.notifyObserver(); // 通知观察者}
}

输出结果如下:

4a3fdd34d436c1325daf6eac38baf014.png

上边的实现主要是为了符合最原始的定义,调用 update 的时候没有传参。如果观察者需要的参数是一致的,其实这里也可以直接把更新后的数据传过去,这样观察者就不需要向上边一样再去调用 subject.getPost() 手动拿更新后的数据了。

这两种不同的方式前者叫做拉 (pull) 模式,就是收到 Subject 的通知后,通过内部的 Subject  对象调用相应的方法去拿到需要的数据。

后者叫做推 (push) 模式,Subject 更新的时候就将数据推给观察者,观察者直接使用即可。

下边用 js 改写为推模式:

const WindLiang = () => {const list = [];let post = "还没更新";return {attach(update) {list.push(update);},detach(update) {let findIndex = -1;for (let i = 0; i < list.length; i++) {if (list[i] === update) {findIndex = i;break;}}if (findIndex !== -1) {list.splice(findIndex, 1);}},notifyObserver() {for (let i = 0; i < list.length; i++) {list[i](post);}},writePost(p) {post = p;},};
};const XiaoMing = {update(post){console.log("我收到了" + post + " 并且点了个赞");}
}const XiaoYang = {update(post){console.log("我收到了" + post + " 并且转发了");}
}const XiaoGang = {update(post){console.log("我收到了" + post + " 并且收藏");}
}windliang = WindLiang();windliang.attach(XiaoMing.update)
windliang.attach(XiaoYang.update)
windliang.attach(XiaoGang.update)windliang.writePost("新文章-观察者模式,balabala")
windliang.notifyObserver()

在 js 中,我们可以直接将 update 方法传给 Subject ,同时采取推模式,调用 update 的时候直接将数据传给观察者,看起来会简洁很多。

代码实现

回到开头的场景,我们可以利用观察者模式将获取地址后的一系列后续操作解耦出来。

// 页面里有三个模块 A,B,C 需要拿到地址后再进行下一步
// A、B、C 三个模块都是不同人写的,提供了不同的方法供我们调用
const observers = []
// 注册观察者
observers.push(A.update)
observers.push(B.next)
obervers.push(C.change)// getAddress 异步请求
getAddress().then(res => {const address = res.address;observers.forEach(update => update(address))
})

通过观察者模式我们将获取地址后的操作解耦了出来,未来有新增模块只需要注册观察者即可。

当 getAddress 很复杂的时候,通过观察者模式会使得未来的改动变得清晰,不会影响到 getAddress 的逻辑。

必要的话也可以把 observers 抽离到一个新的文件作为一个新模块,防止让一个文件变得过于臃肿。

总

观察者模式比较好理解,通过抽象出一个 Subject 和多个观察者,减轻了它们之间的过度耦合。再说简单点就是利用回调函数,异步完成后调用传入的回调即可。但上边写的观察者模式还是有一些缺点:

  • Subject 仍需要自己维护一个观察者列表,进行 push 和 update。

  • 如果有其他的模块也需要使用观察者模式,还需要模块本身再维护一个新的观察者列表,而不能复用之前的代码。

  • Subject 需要知道观察者提供了什么方法以便未来的时候进行回调。

下一篇文章会继续改进上边的写法,观察者模式的本质思想不变(某个对象变化,然后通知其他观察者对象进行更新)。

但写法上会引入一个中间平台,便于代码更好的复用,使得 Subject 和观察者进行更加彻底的解耦,同时给了它一个新的名字「发布订阅模式」。

更多设计模式推荐阅读:

前端的设计模式系列-策略模式

前端的设计模式系列-代理模式

前端的设计模式系列-装饰器模式

本文作者有一个设计模式系列:https://pattern.windliang.wang/ 推荐大家收藏保存学习。

0d7e11324cf5c09c6e27ab74c2524fbe.gif

················· 若川简介 ·················

你好,我是若川,毕业于江西高校。现在是一名前端开发“工程师”。写有《学习源码整体架构系列》20余篇,在知乎、掘金收获超百万阅读。
从2014年起,每年都会写一篇年度总结,已经坚持写了8年,点击查看年度总结。
同时,最近组织了源码共读活动,帮助4000+前端人学会看源码。公众号愿景:帮助5年内前端人走向前列。

a209edb0e6fed8cd9840e6f87f689ff1.png

扫码加我微信 ruochuan02、拉你进源码共读群

今日话题

目前建有江西|湖南|湖北 籍 前端群,想进群的可以加我微信 ruochuan12 进群。分享、收藏、点赞、在看我的文章就是对我最大的支持~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.tpcf.cn/news/274716.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

旅行者 问题_门槛项目:没有旅行者回到他的原籍城市。

旅行者 问题Sohini Mukherjee| MFA| Spring 2020Sohini Mukherjee | 外交部| 2020年Spring Artivive app to see the full Artivive应用程序可查看完整的#AR experience.#AR体验。 Prompt:提示&#xff1a; As second semester, first year graduate students, you are at a …

产品经理懂技术=流氓会武术(zz)

最近七年&#xff0c;我都在做互联网产品&#xff0c;其中前五年分别在创业公司和上市公司里&#xff0c;做别人的产品&#xff1b;近两年在创业&#xff0c;做自己的产品。 我的体会是&#xff1a;产品经理需要懂技术&#xff0c;创业者尤其需要。但前提是你总觉得有股憋不住的…

技术人的七大必备特质

大家好&#xff0c;我是若川。持续组织了8个月源码共读活动&#xff0c;感兴趣的可以 点此加我微信ruochuan12 参与&#xff0c;每周大家一起学习200行左右的源码&#xff0c;共同进步。同时极力推荐订阅我写的《学习源码整体架构系列》 包含20余篇源码文章。历史面试系列。另外…

figma下载_在Figma中进行原型制作的技巧和窍门

figma下载自定义过渡和微交互 (Custom transitions and micro-interactions) Yep, I know that there are a lot of useful built-in transition effects in Figma already, but here I want to talk about custom micro-interactions, complicated transitions and show you h…

布局收藏用

http://www.aa25.cn/layout/index.shtml转载于:https://www.cnblogs.com/OceanChen/archive/2012/07/25/2608882.html

技术日新月异,发展迅速,如何不断扩展视野

技术日新月异&#xff0c;发展迅速&#xff0c;作为一个与时俱进的互联网人&#xff0c;需要不断地学习扩宽视野。今天为大家推荐几个技术领域中出类拔萃的公众号&#xff0c;它们的每一篇推文都值得你点开&#xff01;1叶小钗技术管理 技术转型 公司治理叶小钗&#xff0c;原腾…

不想当全栈的设计师不是_但我不想成为产品设计师

不想当全栈的设计师不是重点 (Top highlight)I’ve made a huge mistake, I thought to myself, as a realization washed over me in the middle of an interview for a product design role.我对自己想&#xff0c;我犯了一个巨大的错误&#xff0c;因为在接受产品设计职务的…

学习 WCF (6)--学习调用WCF服务的各种方法

来自&#xff1a;http://www.cnblogs.com/gaoweipeng/archive/2009/07/26/1528263.html 根据不同的情况&#xff0c;我们可以用不同的方法调用WCF服务&#xff0c;本文简单总结了一下调用WCF的一些方法(代理类&#xff0c;Ajax...)&#xff0c;分享给大家。开发工具调用WCF 这中…

[科普文] Vue3 到底更新了什么?

Vue3 已经发布一段时间了&#xff0c;这个版本从底层实现到上层 API 设计都发生了非常大的变化&#xff0c;但具体改变了些什么呢&#xff1f;一起简单盘点下&#xff1a;一、Composition API使用传统的option配置方法写组件的时候问题&#xff0c;随着业务复杂度越来越高&…

ipados_如何设计具有最新iPadOS 14功能的出色iPad应用

ipadosWe all know that iPad Pro already has a seriously powerful computing power and that it’s possible to create meaningful stuff with Apple Pen.我们都知道iPad Pro已经具有强大的计算能力&#xff0c;并且可以使用Apple Pen创建有意义的东西。 But do we really…

分组显示的ListView分页加载数据

参考&#xff1a; http://www.cnblogs.com/qianxudetianxia/archive/2011/06/07/2074326.html 分组的ListView的拖拽 http://www.cnblogs.com/qianxudetianxia/archive/2011/06/13/2079253.html ListView分页加载数据 http://blog.csdn.net/cjjky/article/details/6898871转载于…

67行JS代码实现队列取代数组,面试官刮目相看

大家好&#xff0c;我是若川。持续组织了8个月源码共读活动&#xff0c;感兴趣的可以 点此加我微信ruochuan12 参与&#xff0c;每周大家一起学习200行左右的源码&#xff0c;共同进步。同时极力推荐订阅我写的《学习源码整体架构系列》 包含20余篇源码文章。历史面试系列。另外…

ux和ui_我怎么知道UI / UX是否适合我?

ux和ui重点 (Top highlight)I’m super excited to be writing this as it’s the first official issue of Visual Q’s! If you don’t already know, this will be a monthly advice column for designers. If you join the newsletter, you’ll receive this before it goe…

HTML4和HTML5的区别[转]

HTML5是最新的HTML标准&#xff0c;或迟或早&#xff0c;所有的web程序员都会发现需要使用到这个最新的标准&#xff0c;而且&#xff0c;很多人都会感觉到&#xff0c;重新开发一个HTML5的网站&#xff0c;要比把一个网站从HTML4迁移到HTML5上容易的多&#xff0c;这是因为这两…

vs2017字体最佳选择_如何为下一个项目选择最佳字体? 一个简单的游戏

vs2017字体最佳选择“If I have the right font, half my design battle is already won!”“如果我使用正确的字体&#xff0c;那么我的设计大战已经赢了一半&#xff01;” In my first UX Design job, my AVP( Satish if you’re reading this, this one’s for you. ) onc…

浅谈初中级前端学习方法~

大家好&#xff0c;我是若川。 常有小伙伴问我如何学习前端开发。今天就简单谈下学习方法&#xff0c;方法可能主要适用于初中级前端。回想我们高中学习&#xff0c;是不是都是"以课本为主&#xff0c;其他资料为辅"。而且课堂上记笔记&#xff0c;然后通过大量练习&…

HDU-水饺基情 二维树状数组

该题就是简单的二维树状数组&#xff0c;保留一份棋盘的最新状态即可&#xff0c;树状数组里面就只保留在原有基础上增加或者减少的某一种饺子的数量。 代码如下&#xff1a; #include <cstring> #include <cstdlib> #include <cstdio> using namespace std;…

ui设计中的版式设计_设计中的版式-第3部分

ui设计中的版式设计and how not to suck at it以及如何不吸吮它 This is the 3rd and last part of the series. Here we take all our learnings from Part 1(Click to read) & Part 2(Click to read) and put to good use. Lets begin!这是本系列的第三部分也是最后一部…

听说你还在用开发者工具手动上传小程序,快来试试 miniprogram-ci 提效摸鱼

大家好&#xff0c;我是若川。持续组织了8个月源码共读活动&#xff0c;感兴趣的可以 点此加我微信ruochuan12 参与&#xff0c;每周大家一起学习200行左右的源码&#xff0c;共同进步。同时极力推荐订阅我写的《学习源码整体架构系列》 包含20余篇源码文章。历史面试系列。另外…

ucla ai_UCLA的可持续性:用户体验案例研究

ucla aiRole: UX Researcher / UX Designer / Critical-thinker角色&#xff1a; UX研究人员/ UX设计人员/批判性思维者 Scope: 4 weeks, March — March 2020范围&#xff1a; 4周&#xff0c;2020年3月至2020年3月 What I Did: UX Research, Speculative Design, Product D…