观察者模式

🤔


观察者模式概述

  • 观察者模式也被称作发布/订阅模式,观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。

观察者模式用处

  • 将一个系统分割成一个一些类相互协作的类有一个不好的副作用,那就是需要维护相关对象间的一致性。我们不希望为了维持一致性而使各类紧密耦合,这样会给维护、扩展和重用都带来不便。观察者就是解决这类的耦合关系的。

观察者模式角色

  1. 抽象主题(Subject):它把所有观察者对象的引用保存到一个聚集里,每个主题都可以有任何数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象。
  2. 具体主题(ConcreteSubject):将有关状态存入具体观察者对象;在具体主题内部状态改变时,给所有登记过的观察者发出通知。
  3. 抽象观察者(Observer):为所有的具体观察者定义一个接口,在得到主题通知时更新自己。
  4. 具体观察者(ConcreteObserver):实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题状态协调。

以下是观察者模式的类图:


观察者模式实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
/// <summary>
/// 抽象主题类
/// </summary>
public abstract class Subject
{
private List<Observer> observers = new List<Observer>();

/// <summary>
/// 增加观察者
/// </summary>
/// <param name="observer"></param>
public void Attach(Observer observer)
{
observers.Add(observer);
}

/// <summary>
/// 移除观察者
/// </summary>
/// <param name="observer"></param>
public void Detach(Observer observer)
{
observers.Remove(observer);
}

/// <summary>
/// 向观察者(们)发出通知
/// </summary>
public void Notify()
{
foreach (Observer o in observers)
{
o.Update();
}
}
}

/// <summary>
/// 抽象观察者类,为所有具体观察者定义一个接口,在得到通知时更新自己
/// </summary>
public abstract class Observer
{
public abstract void Update();
}

/// <summary>
/// 具体观察者或具体通知者,将有关状态存入具体观察者对象;在具体主题的内部状态改变时,给所有登记过的观察者发出通知。具体主题角色通常用一个具体子类实现。
/// </summary>
public class ConcreteSubject : Subject
{
private string subjectState;

/// <summary>
/// 具体观察者的状态
/// </summary>
public string SubjectState
{
get { return subjectState; }
set { subjectState = value; }
}
}

/// <summary>
/// 具体观察者,实现抽象观察者角色所要求的更新接口,已是本身状态与主题状态相协调
/// </summary>
public class ConcreteObserver : Observer
{
private string observerState;
private string name;
private ConcreteSubject subject;

/// <summary>
/// 具体观察者用一个具体主题来实现
/// </summary>
public ConcreteSubject Subject
{
get { return subject; }
set { subject = value; }
}

public ConcreteObserver(ConcreteSubject subject, string name)
{
this.subject = subject;
this.name = name;
}

/// <summary>
/// 实现抽象观察者中的更新操作
/// </summary>
public override void Update()
{
observerState = subject.SubjectState;
Console.WriteLine("The observer's state of {0} is {1}", name, observerState);
}
}

观察者模式解除了主题和具体观察者的耦合,让耦合的双方都依赖于抽象,而不是依赖具体。从而使得各自的变化都不会影响另一边的变化。
但此例中依赖关系并未完全解除,抽象通知者依旧依赖抽象的观察者
在c#中的可以使用事件委托来彻底解除通知者和观察者之间的耦合。
委托:委托是一种引用方法的类型。一旦为委托分配了方法,委托将与该方法有相同的行为。委托方法可以像其它任何方法一样,具有参数和返回值。委托可以看作是对函数(方法)的的抽象,是函数的“类”,委托的实例代表一个(或多个)具体的函数,它可以是多播的。
事件:事件基于委托,为委托提供了一种发布/订阅机制。事件的订阅与取消与我们刚才讲的观察者模式中的订阅与取消类似,只是表现形式有所不同。在观察者模式中,订阅使用方法Attach()来进行;在事件的订阅中使用“+=”。类似地,取消订阅在观察者模式中用Dettach(),而事件的取消用“-=”。


Unity中的观察者模式

  • 在unity游戏开发中,最常用到的设计模式就是单例模式和观察者模式,在之上已经介绍了观察者模式,以下通过代码来直观的了解在unity中,观察者模式的简单使用。
  • RPG游戏中我们拥有自己的角色,角色通常拥有名称和等级,以下程序中通过观察者模式实现点击鼠标左键增加角色等级。

ShowPlayer.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 观察者
/// </summary>
public class ShowPlayer : MonoBehaviour {

public PlayerData mHero;


//设置通知者
public void SetData(PlayerData player){
mHero = player;
//事件绑定
mHero.Register(Show);
Show();
}

private void Show(){
print("Hero's name " + mHero.name);
print("Hero's rank " + mHero.Rank);
}
}

PlayerData.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 数据类
/// 通知者
/// </summary>
public class PlayerData : SubjestBase {

public string name;
private string rank;
public string Rank{
get{
return rank;
}
set{
rank = value;
Notified();
}
}
}

SubjestBase.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
using System;

/// <summary>
/// 通知者基类
/// </summary>
public class SubjestBase {
private event Action eventHander;
//注册事件
public void Register(Action func){
eventHander += func;
}

//删除事件
public void UnRegister(Action func){
eventHander -= func;
}

//发送通知
public void Notified(){
if(eventHander != null){
eventHander();
}
}
}

PlayerSelectCtrl.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 控制器
/// </summary>
public class PlayerSelectCtrl : MonoBehaviour {

private ShowPlayer showPlayer;
private PlayerData player;

void Start () {
showPlayer = GetComponentInChildren<ShowPlayer>();
player = User.GetPlayer();
showPlayer.SetData(player);

}

//数据更新
void Update () {
if(Input.GetMouseButtonDown(0)){
player.Rank = (int.Parse(player.Rank) + 1).ToString();
}
}
}

User.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class User{

public static PlayerData GetPlayer(){
PlayerData player = new PlayerData();
player.name = "Yu";
player.Rank = "7";
return player;
}
}

宇 wechat
扫描二维码,订阅微信公众号