动机(Motivate):
在软件构建 过程中,我们需要为某些对象建立一种“通知依赖关系” --------一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知。如果这样的依赖关系过于紧密,将使软件不能很好地抵御变化。使用面 向对象技术,可以将这种依赖关系弱化,并形成一种稳定的依赖关系。从而实现软件体系结构的松耦合。
意图(Intent):
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时, 所有依赖于它的对象都得到通知并被自动更新。
-------《设计模式》GOF
结构图(Struct):
适用性:
1.当一个抽象模型有两个方面, 其中一个方面依赖于另一方面。将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。
2.当对一个对象的改变需要同时改变其它对象, 而不知道具体有多少对象有待改变。
3
.当一个对象必须通知其它对象,而它又不能假定其它对象是谁。换言之, 你不希望这些对象是紧密耦合的。
生活中的例子:
观 察者定义了对象间一对多的关系,当一个对象的状态变化时,所有依赖它的对象都得到通知并且自动地更新。在ATM取款,当取款成功后,以手机、邮件等方式进行通知。
代码实现:
1
public
class
BankAccount
2
{
3
Emailer emailer;
//
强信赖关系
4
Mobile phoneNumber;
//
强信赖关系
5
6
private
double
_money;
7
8
public
Emailer Emailer
9
{
10
get
{
return
emailer; }
11
set
{
this
.emailer
=
value; }
12
}
13
public
Mobile PhoneNumber
14
{
15
get
{
return
phoneNumber; }
16
set
{
this
.phoneNumber
=
value; }
17
}
18
public
double
Money
19
{
20
get
{
return
_money; }
21
set
{
this
._money
=
value; }
22
}
23
24
public
void
WithDraw()
25
{
26
emailer.SendEmail(
this
);
27
phoneNumber.SendNotification(
this
);
28
}
29
30
}
1
public
class
Emailer
2
{
3
private
string
_emailer;
4
public
Emailer(
string
emailer)
5
{
6
this
._emailer
=
emailer;
7
}
8
public
void
SendEmail(BankAccount ba)
9
{
10
//
..
11
Console.WriteLine(
"
Notified : Emailer is {0}, You withdraw {1:C}
"
, _emailer, ba.Money);
12
}
13
}
1
public
class
Mobile
2
{
3
private
long
_phoneNumber;
4
public
Mobile(
long
phoneNumber)
5
{
6
this
._phoneNumber
=
phoneNumber;
7
}
8
public
void
SendNotification(BankAccount ba)
9
{
10
Console.WriteLine(
"
Notified :Phone number is {0} You withdraw {1:C}
"
, _phoneNumber, ba.Money);
11
}
12
}
此时简单的客户端调用如下:
1
class
Test
2
{
3
static
void
Main(
string
[] args)
4
{
5
BankAccount ba
=
new
BankAccount();
6
Emailer emailer
=
new
Emailer(
"
[email protected]
"
);
7
Mobile mobile
=
new
Mobile(
13901234567
);
8
ba.Emailer
=
emailer;
9
ba.PhoneNumber
=
mobile;
10
ba.Money
=
2000
;
11
ba.WithDraw();
12
}
13
}
运行结果如下:
由此可见程序可以正常运行,但请注意BandAccount和Emailer及Mobile之间形成了一种双向的依赖关系,即BankAccount调用了Emailer及Mobile的方法,而Emailer及Mobile调用了BnadAccount类的属性。如果有其中一个类变化,有可能会引起另一个的变化。如果又需添加一种新的通知方式,就得在BankAccount的
WithDraw()方法中
增加对该中通知方式的调用。
显然这样的设计极大的违背了“开放-封闭”原则,这不是我们所想要的,仅仅是新增加了一种通知对象,就需要对原有的BankAccount类进行修改,这样的设计是很糟糕的。对此做进一步的抽象,既然出现了多个通知对象,我们就为这些对象之间抽象出一个接口,用它来取消BankAccount和具体的通知对象之间依赖。
由此我们由左图转换到右图。
实例代码如下:
1
public
interface
IObserverAccount
2
{
3
void
Update(BankAccount ba);
4
}
1
public
class
BankAccount
2
{
3
IObserverAccount emailer;
//
依赖于接口
4
IObserverAccount phoneNumber;
//
依赖于接口
5
6
private
double
_money;
7
8
public
IObserverAccount Emailer
9
{
10
get
{
return
emailer; }
11
set
{
this
.emailer
=
value; }
12
}
13
public
IObserverAccount PhoneNumber
14
{
15
get
{
return
phoneNumber; }
16
set
{
this
.phoneNumber
=
value; }
17
}
18
public
double
Money
19
{
20
get
{
return
_money; }
21
set
{
this
._money
=
value; }
22
}
23
24
public
void
WithDraw()
25
{
26
emailer.Update(
this
);
27
phoneNumber.Update(
this
);
28
}
29
30
}
1
public
class
Emailer : IObserverAccount
2
{
3
private
string
_emailer;
4
public
Emailer(
string
emailer)
5
{
6
this
._emailer
=
emailer;
7
}
8
public
void
Update(BankAccount ba)
9
{
10
//
..
11
Console.WriteLine(
"
Notified : Emailer is {0}, You withdraw {1:C}
"
, _emailer, ba.Money);
12
}
13
}
1
public
class
Mobile : IObserverAccount
2
{
3
private
long
_phoneNumber;
4
public
Mobile(
long
phoneNumber)
5
{
6
this
._phoneNumber
=
phoneNumber;
7
}
8
public
void
Update(BankAccount ba)
9
{
10
Console.WriteLine(
"
Notified :Phone number is {0} You withdraw {1:C}
"
, _phoneNumber, ba.Money);
11
}
12
}
客户端与上方相同,其运行结果也相同。但BankAccount增加和删除通知对象时,还需对其进行修改。
对此我们再做如下重构,在BankAccount中维护一个IObserver列表,同时提供相应的维护方法。
1
public
class
BankAccount
2
{
3
private
List
<
IObserverAccount
>
Observers
=
new
List
<
IObserverAccount
>
();
4
5
6
private
double
_money;
7
8
public
double
Money
9
{
10
get
{
return
_money; }
11
set
{
this
._money
=
value; }
12
}
13
14
public
void
WithDraw()
15
{
16
foreach
(IObserverAccount ob
in
Observers)
17
{
18
ob.Update(
this
);
19
20
}
21
}
22
public
void
AddObserver(IObserverAccount observer)
23
{
24
Observers.Add(observer);
25
}
26
public
void
RemoverObserver(IObserverAccount observer)
27
{
28
Observers.Remove(observer);
29
}
30
31
}
此时客户端代码如下:
1
class
Test
2
{
3
static
void
Main(
string
[] args)
4
{
5
BankAccount ba
=
new
BankAccount();
6
IObserverAccount emailer
=
new
Emailer(
"
[email protected]
"
);
7
IObserverAccount mobile
=
new
Mobile(
13901234567
);
8
9
ba.Money
=
2000
;
10
ba.AddObserver(emailer);
11
ba.AddObserver(mobile);
12
13
ba.WithDraw();
14
}
15
}
走到这一步,已经有了Observer模式的影子了,BankAccount类不再依赖于具体的Emailer或Mobile,而是依赖于抽象的IObserverAccount。存在着的一个问题是Emailer或Mobile仍然依赖于具体的BankAccount,解决这样的问题很简单,只需要再对BankAccount类做一次抽象。如下图:
1
public
abstract
class
Subject
2
{
3
private
List
<
IObserverAccount
>
Observers
=
new
List
<
IObserverAccount
>
();
4
5
private
double
_money;
6
public
Subject(
double
money)
7
{
8
this
._money
=
money;
9
}
10
11
public
double
Money
12
{
13
get
{
return
_money; }
14
}
15
16
public
void
WithDraw()
17
{
18
foreach
(IObserverAccount ob
in
Observers)
19
{
20
ob.Update(
this
);
21
22
}
23
}
24
public
void
AddObserver(IObserverAccount observer)
25
{
26
Observers.Add(observer);
27
}
28
public
void
RemoverObserver(IObserverAccount observer)
29
{
30
Observers.Remove(observer);
31
}
32
33
}
1
public
interface
IObserverAccount
2
{
3
void
Update(Subject subject);
4
}
1
public
class
BankAccount : Subject
2
{
3
public
BankAccount(
double
money)
4
:
base
(money)
5
{ }
6
7
}
1
public
class
Emailer : IObserverAccount
2
{
3
private
string
_emalier;
4
public
Emailer(
string
emailer )
5
{
6
this
._emalier
=
emailer;
7
}
8
public
void
Update(Subject subject)
9
{
10
Console.WriteLine(
"
Notified : Emailer is {0}, You withdraw {1:C}
"
, _emalier, subject.Money);
11
}
12
}
1
public
class
Mobile : IObserverAccount
2
{
3
private
long
_phoneNumber;
4
public
Mobile(
long
phoneNumber)
5
{
6
this
._phoneNumber
=
phoneNumber;
7
}
8
public
void
Update(Subject subject)
9
{
10
Console.WriteLine(
"
Notified :Phone number is {0} You withdraw {1:C}
"
, _phoneNumber, subject.Money);
11
}
12
}
此时客户端实现如下:
1
class
Test
2
{
3
static
void
Main(
string
[] args)
4
{
5
Subject subject
=
new
BankAccount(
2000
);
6
subject.AddObserver(
new
Emailer(
"
[email protected]
"
));
7
subject.AddObserver(
new
Mobile(
13901234567
));
8
9
subject.WithDraw();
10
}
11
}
推模式与拉模式
对于发布-订阅模型,大家都很容易能想到推模式与拉模式,用SQL Server做过数据库复制的朋友对这一点很清楚。在Observer模式中同样区分推模式和拉模式,我先简单的解释一下两者的区别:推模式是当有消息时,把消息信息以参数的形式传递(推)给所有观察者,而拉模式是当有消息时,通知消息的方法本身并不带任何的参数,是由观察者自己到主体对象那儿取回(拉)消息。知道了这一点,大家可能很容易发现上面我所举的例子其实是一种推模式的Observer模式。我们先看看这种模式带来了什么好处:当有消息时,所有的 观察者都会直接得到全部的消息,并进行相应的处理程序,与主体对象没什么关系,两者之间的关系是一种松散耦合。但是它也有缺陷,第一是所有的观察者得到的 消息是一样的,也许有些信息对某个观察者来说根本就用不上,也就是观察者不能“按需所取”;第二,当通知消息的参数有变化时,所有的观察者对象都要变化。鉴于以上问题,拉模式就应运而生了,它是由观察者自己主动去取消息,需要什么信息,就可以取什么,不会像推模式那样得到所有的消息参数。
拉模式实现如下:
1
public
abstract
class
Subject
2
{
3
private
List
<
IObserverAccount
>
Observers
=
new
List
<
IObserverAccount
>
();
4
5
6
private
double
_money;
7
8
public
double
Money
9
{
10
get
{
return
_money; }
11
}
12
public
Subject(
double
money)
13
{
14
this
._money
=
money;
15
}
16
public
void
WithDraw()
17
{
18
foreach
(IObserverAccount ob
in
Observers)
19
{
20
ob.Update();
21
22
}
23
}
24
public
void
AddObserver(IObserverAccount observer)
25
{
26
Observers.Add(observer);
27
}
28
public
void
RemoverObserver(IObserverAccount observer)
29
{
30
Observers.Remove(observer);
31
}
32
33
}
1
public
interface
IObserverAccount
2
{
3
void
Update();
4
}
1
public
class
BankAccount :Subject
2
{
3
public
BankAccount(
double
money)
4
:
base
(money)
5
{ }
6
7
}
1
public
class
Emailer : IObserverAccount
2
{
3
private
string
_emalier;
4
private
Subject _subject;
5
public
Emailer(
string
emailer,Subject subject)
6
{
7
this
._emalier
=
emailer;
8
this
._subject
=
subject;
9
}
10
public
void
Update()
11
{
12
//
..
13
Console.WriteLine(
"
Notified : Emailer is {0}, You withdraw {1:C}
"
, _emalier,_subject.Money);
14
}
15
}
1
public
class
Mobile : IObserverAccount
2
{
3
private
long
_phoneNumber;
4
private
Subject _subject;
5
public
Mobile(
long
phoneNumber,Subject subject)
6
{
7
this
._phoneNumber
=
phoneNumber;
8
this
._subject
=
subject;
9
}
10
public
void
Update()
11
{
12
Console.WriteLine(
"
Notified :Phone number is {0} You withdraw {1:C}
"
, _phoneNumber,_subject.Money);
13
}
14
}
此时客户端调用如下:
1
class
Test
2
{
3
static
void
Main(
string
[] args)
4
{
5
Subject subject
=
new
BankAccount(
2000
);
6
subject.AddObserver(
new
Emailer(
"
[email protected]
"
,subject));
7
subject.AddObserver(
new
Mobile(
13901234567
,subject));
8
9
subject.WithDraw();
10
}
11
}
.NET中Observer实现:
用事件和委托来实现Observer模式我认为更加的简单和优雅,也是一种更好的解决方案。
1
public
class
Subject
2
{
3
public
event
NotifyEventHandler NotifyEvent;
4
5
private
double
_money;
6
public
Subject(
double
money)
7
{
8
this
._money
=
money;
9
}
10
11
public
double
Money
12
{
13
get
{
return
_money; }
14
}
15
16
public
void
WithDraw()
17
{
18
OnNotifyChange();
19
}
20
public
void
OnNotifyChange()
21
{
22
if
(NotifyEvent
!=
null
)
23
{
24
NotifyEvent(
this
);
25
}
26
27
}
28
29
}
1
public
class
Emailer
2
{
3
private
string
_emalier;
4
public
Emailer(
string
emailer)
5
{
6
this
._emalier
=
emailer;
7
}
8
public
void
Update(
object
obj)
9
{
10
if
(obj
is
Subject)
11
{
12
Subject subject
=
(Subject)obj;
13
14
Console.WriteLine(
"
Notified : Emailer is {0}, You withdraw {1:C}
"
, _emalier, subject.Money);
15
}
16
}
17
}
public
delegate
void
NotifyEventHandler(
object
sender);
客户端调用如下:
1
class
Test
2
{
3
static
void
Main(
string
[] args)
4
{
5
Subject subject
=
new
Subject(
2000
);
6
Emailer emailer
=
new
Emailer(
"
[email protected]
"
);
7
subject.NotifyEvent
+=
new
NotifyEventHandler(emailer.Update);
8
9
10
subject.WithDraw();
11
}
12
}
Observer实现要点:
1.使用面向对象的抽象,Observer模式使得我们可以独立地改变目标与观察者,从而使二者之间的依赖关系达到松耦合。
2.目标发送通知时,无需指定观察者,通知(可以携带通知信息作为参数)会自动传播。观察者自己决定是否需要订阅通知。目标对象对此一无所知。
3
.在C#中的Event。委托充当了抽象的Observer接口,而提供事件的对象充当了目标对象,委托是比抽象Observer接口更为松耦合的设计。