观察者模式
观察者模式是一种对象行为型模式,同时也是设计模式种比较重要的一种
观察者模式(Observer Pattern)定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
字面上理解可以理解为在整个观察者模式中存在两种角色,一类是观察者而另一类是被观察者。向上抽取我们得到大致四个类观察者接口、具体观察者、被观察者接口、具体被观察者
有人可能会有疑问,为什么要有接口,我们需要为那些在目标(被观察者)发生改变时需获得通知的对象定义一个更新接口。而更新时发出的通知方式各不相同。同理被观察者远远不止有一类。类图如下:
主要优点如下:
降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。符合依赖倒置原则。
目标与观察者之间建立了一套触发机制。
主要缺点如下
目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用。
当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率。
如果你接触过发布、订阅相关内容的话,你会发现观察者模式和发布、订阅的方式很像,但是注意二者是有区别的。因为今天聊的是观察者模式,所以我们不讨论发布、订阅相关内容
# 案例
其实观察者模式的应用案例有很多,我在查找相关案例的时候看到一个比较感兴趣的,以游戏为例子。原文链接如下
https://zhuanlan.zhihu.com/p/158537313
需求描述:
角色移动到怪物的有效范围,怪物会袭击角色。角色移动到陷阱的有效范围,陷阱会困住角色。角色移动到宝物的有效范围,宝物会为角色加血。我们需要完成的就是如何让移动移动的事件被怪物、陷阱、道具感知到,并做出正确的反应
如果大佬看到我的文章的话(几率几乎为0)敬请见谅盗图的操作!!!
遥记当年我在写游戏打怪脚本的时候就采用了文中大佬描述的第一种方法,流程如下
实话实讲我当时并不知道设计模式是什么鬼,所以才采用的这种方式,看似没有问题,至少功能是可以实现的。但是问题在于者 1000 毫秒的等待。游戏人物不满足这些条件的时候就会浪费资源。
一道题有 N 种解法,正如文中的作者提出虽然还有另外一种不使用设计模式的玩法,但是同样存在一些问题。我们接下来研究下使用设计模式怎么解这道题目
文中大佬给出了代码不过是Java的,对我来说没啥难度。这里我给它翻译成Delphi版的(这应该不算抄袭)
program ObserverPattern;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils,
UnitObserverPattern in 'UnitObserverPattern.pas';
begin
try
//创建被观察者,即游戏角色
var Role := TRole.Create();
//添加观察者
Role.AddObserver(TMonster.Create);
//角色移动
Role.Move;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
Readln;
end.
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
核心代码单元
unit UnitObserverPattern;
interface
uses
System.Generics.Collections;
type
//观察者,需要有一个更新自身(观察者)状态的方法
IObserver = interface
['{6B71C6A8-01FD-43E0-B4F8-EDCFBFFA911D}']
procedure Update();
end;
//怪物:具体的观察者
TMonster = class(TInterfacedObject, IObserver)
public
procedure Update(); overload;
//判断角色是否在自己的影响范围内,这里忽略细节,直接返回true
function IsInRange(): Boolean;
end;
TTrap = class(TInterfacedObject, IObserver)
//陷阱:具体的观察者
public
procedure Update(); overload;
//判断角色是否在自己的影响范围内,这里忽略细节,直接返回true
function IsInRange(): Boolean;
end;
TSubject = class abstract
//被观察者,定义对于观察者的操作
private
FObserverList: TLIst<IObserver>;
constructor Create(); overload;
public
property ObserverList: TLIst<IObserver> read FObserverList write FObserverList;
//添加观察者
procedure AddObserver(Observer: IObserver);
//删除观察者
procedure DeleteObserver(Observer: IObserver);
//通知所有的观察者
procedure NotifyObservers();
end;
//角色:具体的被观察者
TRole = class(TSubject)
public
//角色移动
procedure Move();
constructor Create(); overload;
end;
implementation
{ TSubject }
procedure TSubject.AddObserver(Observer: IObserver);
begin
Self.ObserverList.Add(Observer);
end;
constructor TSubject.Create;
begin
inherited;
Self.FObserverList := TList<IObserver>.Create;
end;
procedure TSubject.DeleteObserver(Observer: IObserver);
begin
Self.ObserverList.Delete(Self.ObserverList.IndexOf(Observer));
end;
procedure TSubject.NotifyObservers;
begin
for var Observer in ObserverList do begin
Observer.Update;
end;
end;
{ TMonster }
function TMonster.IsInRange: Boolean;
begin
Result := True;
end;
procedure TMonster.Update;
begin
if (Self.IsInRange) then
Writeln('进入怪物的攻击范围');
end;
{ TTrap }
function TTrap.IsInRange: Boolean;
begin
Result := True;
end;
procedure TTrap.Update;
begin
if (Self.IsInRange) then
Writeln('进入陷阱的攻击范围');
end;
{ TRole }
constructor TRole.Create;
begin
inherited Create;
end;
procedure TRole.Move;
begin
Writeln('角色移动');
self.NotifyObservers;
end;
end.
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
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
PS:对于原文中的代码我做了简化,只有两个观察者类,全部实现真的太长了
# 结语
这里主要是回答一下之前有粉丝朋友提出关于私有属性跨单元依然可以调用的问题。我刚才又测试了一遍确认不可以,即使手动写了代码编译依然不会通过。当前我使用的版本是XE10.4.2 ,现在只能将这个诡异的问题归结于Delphi的版本问题了
起初我以为设计模式这个主题不会有人看,因为更新到现在公众号的阅读量少的可怜。而且过分的是我每次更新文章居然还会导致有的朋友取消关注,可是当我发一些非技术文章的时候反而阅读量蹭蹭的涨。虽然很郁闷,但是我依然会更新下去,一方面公众号的文章只是我平时学习的积累,另一方面但凡这些文章能帮到哪怕是一位朋友我也知足了