.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
}

完结了,观察者模式都结束了,现在是2022年08月15日 21:26分,我还在公司加班,回去了,这篇vue源码我看的有点懵逼,多调试就明白了,有时间整理一下步骤,待续
posted @ 2022-08-12 15:53  rookiexwang  阅读(83)  评论(0编辑  收藏  举报