.NET 观察者模式(C#,JS,Vue watch源码)
概念
观察者模式
当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如一个对象被修改时,会通知依赖它的对象。
观察者模式属于行为型设计模式。
- 意图:定义对象间的一种一对多的依赖关系,当对象发生改变时,所有依赖它的对象都会自动到通知并更新。不主动触发,而是被动监听,让二者产生解耦
- 主要解决: 一个对象改变通知其他对象,主要是考虑低耦合,高度协作
- 何时使用: 一个对象被修改时,会通知依赖它的对象
- 如何使用:使用面向对象技术,可以将类之间的依赖关系弱化
C# 的 观察者模式
简单版本实现(1.0)
1.创建一个控制台项目,名称叫:ObServerPattern.Console
2.新增Sentry,Sergeant类
3.Sentry代码如下:
Sentry
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace ObServerPattern.Console
{
/// <summary>
/// 哨兵
/// </summary>
public class Sentry
{
// 士官对象集合
private List<Sergeant> _sergeants = new List<Sergeant>();
//敌情
private string _situation;
public string Situation { get => _situation; set => _situation = value; }
/// <summary>
/// 添加需要通知的士官
/// </summary>
/// <param name="sergeant"></param>
public void AddSergeant(Sergeant sergeant)
{
_sergeants.Add(sergeant);
}
/// <summary>
/// 通知士官
/// </summary>
public void Notify()
{
foreach (var _sergeant in _sergeants)
{
_sergeant.Update();
}
}
}
}
4.Sergeant代码如下
Sergeant
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace ObServerPattern.Console
{
/// <summary>
/// 士官
/// </summary>
public class Sergeant
{
private string Name;
private readonly Sentry _sentry;
public Sergeant(string name, Sentry sentry)
{
this._sentry = sentry;
this.Name = name;
}
public void Update()
{
System.Console.WriteLine($"{_sentry.Situation}!!!!!{Name} 迅速向连长报告");
}
}
}
5.program上端调用代码如下:
program
// See https://aka.ms/new-console-template for more information
using ObServerPattern.Console;
Console.WriteLine("Hello, World!");
//注意哨兵和士官是一对多
//实例化哨兵
Sentry sentry = new();
sentry.Situation = "发现敌情";
//实例化多个士官
Sergeant sergeant1 = new("士官1", sentry);
Sergeant sergeant2 = new("士官2", sentry);
Sergeant sergeant3 = new("士官3", sentry);
sentry.AddSergeant(sergeant1);
sentry.AddSergeant(sergeant2);
sentry.AddSergeant(sergeant3);
sentry.Notify();
抽象版本实现(2.0)
1.将哨兵类抽象成一个接口,让哨兵类继承这个接口:
ISentry
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace ObServerPattern.Console
{
/// <summary>
/// 哨兵接口
/// </summary>
public interface ISentry
{
string Situation { get; set; }
void AddSergeant(Sergeant sergeant);
void Notify();
}
}
2.将士官类改造一下,改造成抽象类:
Sergeant
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace ObServerPattern.Console;
/// <summary>
/// 士官
/// </summary>
public abstract class Sergeant
{
protected string Name;
protected readonly Sentry _sentry;
public Sergeant(string name, Sentry sentry)
{
this._sentry = sentry;
this.Name = name;
}
public abstract void Update();
}
3.新增一个奥特曼士官类:
UltramanSergeant
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace ObServerPattern.Console
{
public class UltramanSergeant : Sergeant
{
public UltramanSergeant(string name, Sentry sentry) : base(name, sentry)
{
}
public override void Update()
{
System.Console.WriteLine($"{_sentry.Situation}!!!!!{Name} 迅速向连长报告");
}
}
}
4.program上层调用方式修改:
点击查看代码
// See https://aka.ms/new-console-template for more information
using ObServerPattern.Console;
Console.WriteLine("Hello, World!");
//注意哨兵和士官是一对多
//实例化哨兵
Sentry sentry = new();
sentry.Situation = "发现敌情";
//实例化多个士官
Sergeant sergeant1 = new UltramanSergeant("Ace", sentry);
Sergeant sergeant2 = new UltramanSergeant("Taro", sentry);
Sergeant sergeant3 = new UltramanSergeant("Leo", sentry);
sentry.AddSergeant(sergeant1);
sentry.AddSergeant(sergeant2);
sentry.AddSergeant(sergeant3);
sentry.Notify();
代理模式与观察者模式结合(3.0)
1.改造Sentry 哨兵类
get和set方法是代理模式,具体的代理模式是个什么,后面会写文章介绍一下
发现在我们的上端得调用哨兵类的Notify方法,为了方便,直接在哨兵类赋值敌情属性的set方法里调用Notify方法
代码如下:
Sentry
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace ObServerPattern.Console
{
/// <summary>
/// 哨兵
/// </summary>
public class Sentry : ISentry
{
// 士官对象集合
private List<Sergeant> _sergeants = new List<Sergeant>();
//敌情
private string _situation;
public string Situation
{
get => _situation;
set
{
_situation = value;
this.Notify();
}
}
/// <summary>
/// 添加需要通知的士官
/// </summary>
/// <param name="sergeant"></param>
public void AddSergeant(Sergeant sergeant)
{
_sergeants.Add(sergeant);
}
/// <summary>
/// 通知士官
/// </summary>
public void Notify()
{
foreach (var _sergeant in _sergeants)
{
_sergeant.Update();
}
}
}
}
2.改造士官类Sergeant
在上端调用方式中可以看到我们需要循环调用AddSergeant(添加士官的方法),直接将士官类(Sergeant)的构造函数注入的哨兵类(Sentry)换成哨兵接口(ISentry),
构造函数注入的之后,直接构造函数中写:sentry.AddSergeant(this); 哨兵类调用添加士官的方法
代码如下:
点击查看代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace ObServerPattern.Console;
/// <summary>
/// 士官
/// </summary>
public abstract class Sergeant
{
protected string Name;
protected readonly ISentry _sentry;
public Sergeant(string name, ISentry sentry)
{
this._sentry = sentry;
this.Name = name;
sentry.AddSergeant(this);
}
public abstract void Update();
}
3.上端调用方式就变成了如下方法:
注意,发现敌情的赋值,必须写在士官类实例化下面,因为我们是在敌情的set方法中调用的通知士官的方法
点击查看代码
// See https://aka.ms/new-console-template for more information
using ObServerPattern.Console;
Console.WriteLine("Hello, World!");
//注意哨兵和士官是一对多
//实例化哨兵
Sentry sentry = new();
//实例化多个士官
Sergeant sergeant1 = new UltramanSergeant("Ace", sentry);
Sergeant sergeant2 = new UltramanSergeant("Taro", sentry);
Sergeant sergeant3 = new UltramanSergeant("Leo", sentry);
sentry.Situation = "发现敌情";
// sentry.AddSergeant(sergeant1);
// sentry.AddSergeant(sergeant2);
// sentry.AddSergeant(sergeant3);
// sentry.Notify();
事件实现观察者(4.0)
1.修改哨兵接口(ISentry):
因为要用事件去实现观察者,所以定义一个委托,与事件
点击查看代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace ObServerPattern.Console
{
/// <summary>
/// 定义一个委托类型 Fire
/// 专门用于接收方法(用于接收无参数无返回值的方法)
/// </summary>
public delegate void Fire();
/// <summary>
/// 哨兵接口
/// </summary>
public interface ISentry
{
#region 标准观察者
// string Situation { get; set; }
// void AddSergeant(Sergeant sergeant);
// void Notify();
#endregion
#region 事件观察者
// 添加事件
// 根据委托类型定义一个OnFire的事件
event Fire OnFire;
void Notify();
string Situation { get; set; }
#endregion
}
}
2.哨兵类(Sentry)修改:
Sentry
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace ObServerPattern.Console
{
/// <summary>
/// 哨兵
/// </summary>
public class Sentry : ISentry
{
#region 标准的观察者
// 士官对象集合
// private List<Sergeant> _sergeants = new List<Sergeant>();
// //敌情
// private string _situation;
// public string Situation
// {
// get => _situation;
// set
// {
// _situation = value;
// this.Notify();
// }
// }
// /// <summary>
// /// 添加需要通知的士官
// /// </summary>
// /// <param name="sergeant"></param>
// public void AddSergeant(Sergeant sergeant)
// {
// _sergeants.Add(sergeant);
// }
// /// <summary>
// /// 通知士官
// /// </summary>
// public void Notify()
// {
// foreach (var _sergeant in _sergeants)
// {
// _sergeant.Update();
// }
// }
#endregion
#region 事件观察者
//敌情
private string _situation;
public string Situation
{
get => _situation;
set
{
_situation = value;
this.Notify();
}
}
public event Fire OnFire;
public void Notify()
{
OnFire();
}
#endregion
}
}
当给敌情赋值时,就会触发Notify(通知士官)的方法,就会触发事件,就会触发Update方法
3.士官类(Sergeant)改造如下:
事件就是多播委托,事件+=之后某个方法,当事件触发的时候,就相当于调用了你传递的方法
Sergeant
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace ObServerPattern.Console;
/// <summary>
/// 士官
/// </summary>
public abstract class Sergeant
{
#region 标准观察者
// protected string Name;
// protected readonly ISentry _sentry;
// public Sergeant(string name, ISentry sentry)
// {
// this._sentry = sentry;
// this.Name = name;
// sentry.AddSergeant(this);
// }
// public abstract void Update();
#endregion
#region 事件实现观察者
protected readonly ISentry _sentry;
protected readonly string _name;
public Sergeant(string name, ISentry sentry)
{
_name = name;
_sentry = sentry;
//事件其实就是多播委托,这里就是相当于事件触发调用的时候,直接调用了update
_sentry.OnFire += Update;
}
public abstract void Update();
#endregion
}
4.奥特曼士官(UltramanSergeant )改造如下:
UltramanSergeant
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace ObServerPattern.Console
{
public class UltramanSergeant : Sergeant
{
public UltramanSergeant(string name, Sentry sentry) : base(name, sentry)
{
}
public override void Update()
{
System.Console.WriteLine($"{_sentry.Situation}!!!!!{_name} 迅速向连长报告");
}
}
}
最终运行结果如下:
没懂的可以debug一下,在上端调用初始化类的时候,一层层debug下去你就明白了
C# 版的观察者模式告一段落
--------------------------2022-08-14结束---------------------
下面开始写js的观察者模式
JS版本的观察者模式
es6实现标准观察者 简单介绍js的基类继承方法
新建一个文件ObServerPattern.js
1.目标类(Subject)如下:
Subject
class Subject {
constructor() {
//设置私有成员变量 state ,设置state时触发观察者
this.state = 0
//用于存放观察者
this.observerList = []
}
getState() {
return this.state
}
setState(value) {
this.state = value
this.notify()
}
/**
* 通知/调用观察者类 的 update方法
*/
notify() {
this.observerList.forEach((el) => {
el.update()
})
}
/**
* 添加观察者
* @param {*} observer
*/
attach(observer) {
this.observerList.push(observer)
}
/**
* 移除观察者
* @param {*} observer
*/
detach(observer) {
//findIndex找到当前对象在list集合中的下标
var index = this.observerList.findIndex(observer)
//splice 第一个参数是要删除的index ,第二个参数是要删除的数量
this.observerList.splice(index, 1)
}
}
2.观察者(Observer)类
点击查看代码
/**
* 观察者类
*/
class Observer {
/**
* 构造函数
* @param {*} name 观察者名称
* @param {*} subject 目标类/士官类
*/
constructor(name, subject) {
this.name = name
this.subject = subject
subject.attach(this)
}
update ()
{
console.log(`${this.name} is updating,this state is ${this.subject.getState()}`);
}
}
3.调用方式如下:
点击查看代码
var subject = new Subject()
var observer1 = new Observer('test1', subject)
var observer2 = new Observer('test2', subject)
var observer3 = new Observer('test3', subject)
subject.state = 1
subject.notify()
结果如下:
js的继承用法讲解
和java一样,extends继承基类,import 继承接口
新建一个SubjectBase 基类
点击查看代码
class SubjectBase {
constructor() {
//用于存放观察者
this.observerList = []
}
/**
* 通知/调用观察者类 的 update方法
*/
notify() {
this.observerList.forEach((el) => {
el.update()
})
}
/**
* 添加观察者
* @param {*} observer
*/
attach(observer) {
this.observerList.push(observer)
}
/**
* 移除观察者
* @param {*} observer
*/
detach(observer) {
//findIndex找到当前对象在list集合中的下标
var index = this.observerList.findIndex(observer)
//splice 第一个参数是要删除的index ,第二个参数是要删除的数量
this.observerList.splice(index, 1)
}
}
因为基类中包含了detach,attach,notify方法,所以Subject不需要有这些方法
目标类写法如下:class Subject extends SubjectBase
其他的删掉几个多余方法就好了,代码简单,我这里就不写了。
在子类构造函数中写super()
,这样才可以调用父类的方法,要不然是不可以调用父类的方法的。
因为在一个js文件中写的,父类必须在子类之前定义才生效,和后端不同
--------------------------2022-08-14 23:51结束---------------------
--------------------------2022-08-14 23:59开始---------------------
激动的心,颤抖的手,要开始手写watch源码了,骚年了,准备好了吗?
前情提要:
因为watch源码是代理模式与观察者模式相结合的,所以要用到代理模式的代码,代理模式相关文章我已经发出来了,想要看的小伙伴请翻阅我之前的文章,传送门:.NET 代理模式
废话不多说,发车了
watch源码
接着上篇文章的内容,上篇文章写到自己手写vue,将data里面的对象与字段挂载到vue实例中,直接通过实例化出来的vue实例进行调用对象
1.index.html代码改造如下:
增加了watch对象,添加了userName的监听方法
watch
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script> -->
<script src="./myVue.js"></script>
<title>Document</title>
</head>
<body>
<div id="app">
{{userName}}
</div>
<script>
var vm = new Vue({
data: {
userName: "Ace"
},
watch: {
userName (newValue, oldValue) {
}
}
})
vm.user.userName = "GaoSi"
</script>
</body>
</html>
2.在myVue.js改造
- 在initState方法中,加入initWatch()方法 (初始化watch对象)
- 新增initWatch方法,初始化watch
点击查看代码
/**
* Vue类的创建
* @param {*} options vue实例中的众多对象 (data,method,el)
*/
function Vue(options) {
this._init(options)
}
/**
* 创建一个方法用于获取vue对象中的众多属性,并把这个方法赋值给Vue的原型对象_init
* @param {*} options vue实例中的众多对象 (data,method,el)
*/
Vue.prototype._init = function (options) {
//此时this指向Vue类
let vm = this
console.log(`this 为:${this}`)
//创建Vue的对象 $options,来接收传入的options对象
vm.$options = options
//初始化vue
initState(vm)
}
/**
* 初始化vue
* @param {*} vm vue对象
*/
function initState(vm) {
let opt = vm.$options
// 初始化data对象
if (opt.data) {
initData(vm)
}
// 初始化watch
// 前端判断时,不需要写是否为null,不为空则进入,为空则跳过
if (opt.watch) {
initWatch(vm)
}
}
/**
* 初始化Data
* @param {*} vm vue对象
*/
function initData(vm) {
//获取data
let data = vm.$options.data
//vue 对象中创建$data 方法
//data || {} 表示如果data为null则直接返回{}空对象
vm.$data = data || {}
if (typeof data != 'object' || data == null) {
return
} else {
new Observe(data, vm)
}
}
/**
* 监听类 用于监听data的变化
* @param {*} data 数据对象
* @param {*} vm vue对象
*/
function Observe(data, vm) {
/**
* 重构data 并对其进行get/set 属性代理
* @param {*} data
* @param {*} vm
*/
function restructure(data, vm) {
let keys = Object.keys(data)
console.log(keys)
// 循环遍历每一个key 并进行代理化改造
// ★核心内容
for (let index = 0; index < keys.length; index++) {
let key = keys[index]
let value = data[key]
//先定义vm中的对象,就不会出现后面重复定义的错误
vm[key] = value
// 验证value是否依旧是对象(递归)
if (typeof value != 'object' || value == null) {
//啥都不做
// vm[key] = value
// console.log('到了')
// console.log(vm)
} else {
new Observe(value, vm)
}
// 实例化Dep(哨兵)
let dep = new Dep()
// 给data中的变量配置get/set属性用于监听
//或者可以理解成把指定key挂载到data中
Object.defineProperty(data, key, {
get: function () {
if (Dep.target) {
dep.depend()
}
return value
},
set: function (newValue) {
if (newValue != value) {
//验证newValue是否是对象
if (typeof newValue != 'object' || newValue == null) {
//啥都不做
} else {
new Observe(newValue, vm)
}
value = newValue
data.notify()
//这里只是做一个日志输出,如果是一个正常的vue,则应该是更新视图方法
console.log(`我现在是[${value}] 啦`)
}
},
})
//直接将data中的变量挂载到vm中
//注意: 如果之前vm中未曾定义过这些属性,若是有两个相同的key则会出现重复定义属性的错误
//解决方案:在之前每一次for循环后先通过vm[key] = value
//定义一次,那么在这里就不会再次定义了
//这和js中xxx.xxx的意思一样,如果xxx.xx的xx在xxx中不存在则会定义,存在则只是调用
Object.defineProperty(vm, key, {
get: function () {
if (Dep.target) {
dep.depend()
}
return value
},
set: function (newValue) {
if (newValue != value) {
//验证newValue是否是对象
if (typeof newValue != 'object' || newValue == null) {
//啥都不做
} else {
new Observe(newValue, vm)
}
value = newValue
dep.notify()
console.log(`我现在是[${value}] 啦`)
}
},
})
}
}
restructure(data, vm)
}
// 开始watch之旅
/**
* 初始化watch
* @param {*} vm
*/
function initWatch(vm) {
//获取vue中的watch对象
let watch = vm.$options.watch
//循环获取watch中的key
for (const key in watch) {
// console.log(key)
//根据key 获取 watch中的方法
handler = watch[key]
//创建watch
createWatch(vm, key, handler)
}
}
/**
* 创建watch
* @param {*} vm vue实例对象
* @param {*} key watch中监听方法key
* @param {*} handler watch中各个监听方法
*/
function createWatch(vm, key, handler) {
vm.$watch(key, handler)
}
/**
* 在vue实例中扩展$watch方法
* @param {*} key
* @param {*} handler
*/
Vue.prototype.$watch = function (key, handler) {
let vm = this
//实例化watch对象,用于监听watch需要监听的数据
new Watcher(vm, key, handler)
}
/**
* 观察者类(理解成士官类)
*/
class Watcher {
constructor(vm, key, handler) {
this.vm = vm
this.key = key
this.handler = handler
//哨兵类
this.deps = []
// Set对象允许存储任何类型的唯一值,无论是原始值或者对象引用
this.depsId = new Set()
this.getter = function () {
//split 分割字符串
let keys = key.split('.')
//reduce 是一个累加数组
return keys.reduce((total, current) => {
console.log('total是')
console.log(total)
//这一步相当于从vue对象中获取例如是userName的键
return total[current]
}, vm)
}
this.oldValue = this.get()
}
/**
* 根据watch中的key 获取 data中的变量
* @returns 返回data中与watch的key相当应的变量(旧值)
*/
get() {
//渲染watcher到Dep.target上
pushTarget(this)
//调用构造函数中的方法得call一下才可以使用(相当于委托的invoke)
let value = this.getter.call()
popTarget()
return value
}
/**
* 触发更新
* 当data中对应watch的值发生变化后触发
*/
update() {
//获取data对应key改变后的新值
let newValue = this.get()
if (this.oldValue != newValue) {
this.handler(newValue, this.oldValue)
}
}
addDep(dep) {
dep.addWatch(this)
}
}
/**
* 目标类 (看作哨兵)
*/
class Dep {
constructor() {
//存储观察者(士官)的容器
this.watchers = []
}
/**
* 添加观察者/在哨兵的笔记本中记录需要通知的士官
* @param {*} watcher 观察者
*/
addWatch(watcher) {
this.watchers.push(watcher)
}
/**
* Dep.Target此时经过了pushTarget方法,已经是watcher对象了
*
*
* 作用:是把dep(哨兵)自动传递给士官,而不是之前举的例子,需要先实例化士官,再把哨兵通过士官的构造函数传入
*/
depend() {
if (Dep.target) {
Dep.target.addDep(this)
}
}
notify() {
this.watchers.forEach((w) => {
w.update()
})
}
}
/**
* 增加
* @param {*} watcher
*/
function pushTarget(watcher) {
Dep.target = watcher
}
/**
* 移除
*/
function popTarget() {
Dep.target = null
}