观察者模式,简单的讲就是有在观察。有观察的对象也有被观察的对象。彼此之间的关系不是调用。而是观察。首先是观察的对象,比如:农民关心天气,旅游者关心天气,上班的人关心天气,....这里面,农民,旅游者,上班的人都是观察者。他们都有一个特点:关心天气。因此我们可以设计一个接口IObserver。标明他们有个共同的特点,关注天气。他们之间又有不同,就是关注天气后的动作。如农民关心天气后的动作和旅游者不一样。 农民一看到明天下雨,就不浇菜了。旅游者一看到下雨,明天就不出游了。那么每个观察者实现了同样的接口,但具体的实现不一样。好,回过头来看看被观察者,就是气象台了。发布信息的地方。当然,观察者关心是天气信息。因此可能只是条信息。那么被观察者呢,发出这条信息。当然很有可能不会是一个气象台。那么我们同样定义一个接口,只要实现发布天气就好了。叫IObservable.可被观察对象。 这样气象局也好,其他的一些只要能发布气象信息的机构都可以实现这个接口提供服务了。
说完了主体。现在说最重要的:观察。 观察者和被观察者之间的纽带。
那么现实生活中,如何做到呢?一种,当天气变更。气象台发出通知。一种,农民,旅游者,上班人 当需要知道气象信息向气象台跑,获取信息。
第一种,需要考虑的是我发出通知,如果人家不需要呢,那通知就白发了。第二种,如果气象没有发生改变呢,也白跑了。
其实也是,常见的推和拉。推,即气象台主动推销,拉,则观察者主动打听信息。
其实两种都有个目标受众群的概念,气象台需要知道哪些人想知道信息。而受众想知道气象是否发生变化。这样才利于消息的传递。因此需要建立联系的纽带,我们这边简单设计一个hastable。就是通知人名单。
我们来从代码层面看,观察者模式的演化过程:
版本1:
public static void main()
{
TextBox txtWeather = new TextBox ();
bool
isChange = (txtWeather.Text !=
""
);
if
(isChange)
{
//
农民观察天气行为
System.IO.
File
.AppendAllText(
"c:\\farmer.txt"
, txtWeather.Text);
//
旅游者观察天气行为
System.IO.
File
.AppendAllText(
"c:\\tourist.txt"
, txtWeather.Text);
//
上班族
System.IO.
File
.AppendAllText(
"c:\\officeWorker.txt"
, txtWeather.Text);
}
}
当天气发生变化,就执行三者的行为。
这并不是一个面向对象的设计方式,是一个面向过程设计。
一个程序顺序执行下来,没有任何对象的概念。
没有面向对象的设计方式,最大的问题是,大量的重复代码,比如当有大量的其他地方需要调用农民的观察到天气行为时,
System.IO.File.AppendAllText("c:\\farmer.txt", txtWeather.Text);
这段代码将遍布整个项目。当然,如果不变,也就是该操作永远不变,则无需考虑此无问题。为了,解决上述代码的统一管理,我们可能这样来处理。
版本2:
public
class
Demo
{
public
static
void
main()
{
TextBox
txtWeather =
new
TextBox
();
bool
isChange = (txtWeather.Text !=
""
);
if
(isChange)
{
//
农民观察天气行为
FarmerActionAfferWeartherChanged();
//
旅游者观察天气行为
TouristActionAfferWearcherChanged();
//
上班族
OfficeWorkerActionAfferWeartherChanged();
}
}
public
void
FarmerActionAfferWeartherChanged()
{
System.IO.
File
.AppendAllText(
"c:\\farmer.txt"
, txtWeather.Text);
}
public
void
TouristActionAfferWearcherChanged()
{
System.IO.
File
.AppendAllText(
"c:\\tourist.txt"
, txtWeather.Text);
}
public
void
OfficeWorkerActionAfferWeartherChanged()
{
System.IO.
File
.AppendAllText(
"c:\\officeWorker.txt"
, txtWeather.Text);
}
}
这种处理也是面向过程代码通常采用的方式,将公共代码抽离出来,如果以后需要改动,则改动对应的方法即可。
我们接下来考虑这样的变化,如果天气变化导致的行为不仅仅是上诉的几个观察者,并且并不是所有的对象在每次天气发生变化时需要得到通知。此时我们就需要对
if (isChange)
{
//
农民观察天气行为
FarmerActionAfferWeartherChanged();
//
旅游者观察天气行为
TouristActionAfferWearcherChanged();
//
上班族
OfficeWorkerActionAfferWeartherChanged();
}
这段代码进行修正,同时新增不同的的观察者的行为。
我们发现,修改这2处代码都在同一个类中,同时这个类包含大量其他的逻辑(面向过程设计)。类的职责不明确,或者说职责太多,不利于理解。修改一处代码,可能导致其他逻辑出错,太过混乱。因此我们需要进行对这个大的类进行职责划分,不同的职责划分到不同的类中,避免不必要的混乱。我们进行如下修改:
版本3:
public class Demo
{
public
static
void
main()
{
TextBox
txtWeather =
new
TextBox
();
bool
isChange = (txtWeather.Text !=
""
);
if
(isChange)
{
//
农民观察天气行为
Farmer
farmer =
new
Farmer
();
farmer.OnWeartherChanged(txtWeather.Text);
//
旅游者观察天气行为
Toursist
toursist =
new
Toursist
();
toursist.OnWeartherChanged();
//
上班族
OfficeWorker
officeWorker =
new
OfficeWorker
();
officeWorker.OnWeartherChanged();
}
}
}
//Farmer.cs
public
class
Farmer
{
public
void
OnWeartherChanged(
string
weather)
{
System.IO.
File
.AppendAllText(
"c:\\farmer.txt"
, weather);
}
}
//Toursist
public class Toursist
{
public
void
OnWeartherChanged(
string
weather)
{
System.IO.
File
.AppendAllText(
"c:\\tourist.txt"
, weather);
}
}
//OfficeWorker
public class OfficeWorker
{
public
void
OnWeartherChanged(
string
weather)
{
System.IO.
File
.AppendAllText(
"c:\\officeWorker.txt"
, weather);
}
}
这个版本已经是面向对象版本的设计了。我们此处将的观察者模式,是面向对象设计的观察者模式,模式是基于面向对象设计的。所以,掌握设计模式,首先需要熟悉,并应用面向对象设计。才能谈面向对象设计模式的学习。
面向对象设计的几个原则:
开放-封闭原则是面向对象的可复用设计的基石
。
其他设计原则(里氏代换原则、依赖倒转原则、合成/聚合复用原则、迪米特法则、接口隔离原则)是实现开放-封闭原则的手段和工具。
开放-封闭原则:
一个软件实体应当对扩展开放,对修改关闭。即在设计一个模块的时候,应当使这个模块可以在不被修改的前提下被扩展。
我们看上面这几个类,如果需要新增观察对象,则只要新增一个类对象,并实现其方案。并修改Main方法调用。
目前来看,我们支持扩展,但还没有完全达到在不被修改的前提下扩展。
目前版本Demo类依赖与具体的实现
Farmer类,
Toursist类和
OfficeWorker类。
具有强依赖。
我们进行如下变更:
版本4:
public class Demo
{
public
static
void
main()
{
TextBox
txtWeather =
new
TextBox
();
bool
isChange = (txtWeather.Text !=
""
);
if
(isChange)
{
//
农民观察天气行为
IObserver
farmer =
new
Farmer
();
farmer.OnWeartherChanged(txtWeather.Text);
//
旅游者观察天气行为
IObserver
toursist =
new
Toursist
();
toursist.OnWeartherChanged();
//
上班族
IObserver
officeWorker =
new
OfficeWorker
();
officeWorker.OnWeartherChanged();
}
}
}
public
interface
IObserver
{
public
void
OnWeartherChanged(
string
weather);
}
public
class
Farmer
:
IObserver
{
public
void
OnWeartherChanged(
string
weather)
{
System.IO.
File
.AppendAllText(
"c:\\farmer.txt"
, weather);
}
}
public
class
Toursist
:
IObserver
{
public
void
OnWeartherChanged(
string
weather)
{
System.IO.
File
.AppendAllText(
"c:\\tourist.txt"
, weather);
}
}
public
class
OfficeWorker
:
IObserver
{
public
void
OnWeartherChanged(
string
weather)
{
System.IO.
File
.AppendAllText(
"c:\\officeWorker.txt"
, weather);
}
}
我们对具体的几个实现进行了抽象,抽出共同的行为IObserver接口。
但我们发现,Demo类对IObserver,Farmer,Tourist,OfficeWorker都依赖。
反而多了个接口的依赖,并不是一个好的实现,其实,在学习设计模式过程中,经常会看到这样的半途设计的代码。只知道需要对类进行抽象,并没达到真正想要的解耦和。
我们对Demo类进行分析,发现Demo方法还存在进行变化的可能,比如新增一个观察对象等,因此我们对Demo进行进一步的职责分离。改进如下:
版本5:
public
class
Demo
{
public
static
void
main()
{
TextBox
txtWeather =
new
TextBox
();
bool
isChange = (txtWeather.Text !=
""
);
if
(isChange)
{
Observable
observable =
new
Observable
();
observable.observerList.Add(
new
Farmer
());
observable.WeartherChanged(txtWeather.Text);
}
}
}
public
class
Observable
{
public
List
<
IObserver
> observerList =
new
ArrayList<
IObserver
>();
public
void
WeartherChanged(
string
wearther)
{
foreach
(
IObserver
observer
in
observerList)
{
observer.OnWeartherChanged(wearther);
}
}
}
public
interface
IObserver
{
public
void
OnWeartherChanged(
string
weather);
}
public
class
Farmer
:
IObserver
{
public
void
OnWeartherChanged(
string
weather)
{
System.IO.
File
.AppendAllText(
"c:\\farmer.txt"
, weather);
}
}
public
class
Toursist
:
IObserver
{
public
void
OnWeartherChanged(
string
weather)
{
System.IO.
File
.AppendAllText(
"c:\\tourist.txt"
, weather);
}
}
public
class
OfficeWorker
:
IObserver
{
public
void
OnWeartherChanged(
string
weather)
{
System.IO.
File
.AppendAllText(
"c:\\officeWorker.txt"
, weather);
}
}
这个版本,就有点到达观察者模式的效果了。
Demo类依赖于
Observable
,
Farmer
Observable
依赖于
Farmer。
我们
发现被观察者Observale也有可能会有多个发布对象,因此对Observable进行抽象:
版本6:
public class Demo
{
public
static
void
main()
{
TextBox
txtWeather =
new
TextBox
();
bool
isChange = (txtWeather.Text !=
""
);
if
(isChange)
{
Observatory
observable =
new
Observatory
();
observable.observerList.Add(
new
Farmer
());
observable.WeartherChanged(txtWeather.Text);
}
}
}
public
abstract
class
Observable
{
public
List
<
IObserver
> observerList =
new
ArrayList<
IObserver
>();
public
void
WeartherChanged(
string
wearther)
{
foreach
(
IObserver
observer
in
observerList)
{
observer.OnWeartherChanged(wearther);
}
}
}
public
class
Observatory
:
Observable
{
}
public
interface
IObserver
{
public
void
OnWeartherChanged(
string
weather);
}
public
class
Farmer
:
IObserver
{
public
void
OnWeartherChanged(
string
weather)
{
System.IO.
File
.AppendAllText(
"c:\\farmer.txt"
, weather);
}
}
public
class
Toursist
:
IObserver
{
public
void
OnWeartherChanged(
string
weather)
{
System.IO.
File
.AppendAllText(
"c:\\tourist.txt"
, weather);
}
}
public
class
OfficeWorker
:
IObserver
{
public
void
OnWeartherChanged(
string
weather)
{
System.IO.
File
.AppendAllText(
"c:\\officeWorker.txt"
, weather);
}
}
我们查看观察者模式关系类图:
Subject对应Observable
ConcreteSubject对应Observatory
Observer对应IObserver
Farmer对应ConcreteObserver
当我们面对变化时:
比如我们新增一个观察对象:Worker
我们只要新增一个类Worker继承Observer。
然后在Demo中如果需要关注,则新增关注
observable.observerList.Add(
new
Worker
());
除此以外的类都是稳定的,我们不需要更改。
事实上.Net中的事件模式也是观察者模式,我们这个例子用事件方式改进一下看看:
版本7:
public class Demo
{
public
static
void
main()
{
TextBox
txtWeather =
new
TextBox
();
bool
isChange = (txtWeather.Text !=
""
);
if
(isChange)
{
Observatory
observable =
new
Observatory
();
observable.WearcherChanged +=
new
Observatory
.
WeartherChangedHandle
(
new
Farmer
().OnWeartherChanged);
observable.onWearcherChanged(txtWeather.Text);
}
}
}
public
class
Observatory
:Observable
{
public
delegate
void
WeartherChangedHandle
(
string
wearther);
public
event
WeartherChangedHandle
WearcherChanged;
public
void
onWearcherChanged(
string
wearther)
{
if
(WeartherChanged !=
null
)
{
WeartherChanged(wearther);
}
}
}
public
interface
IObserver
{
public
void
OnWeartherChanged(
string
weather);
}
public
class
Farmer
:
IObserver
{
public
void
OnWeartherChanged(
string
weather)
{
System.IO.
File
.AppendAllText(
"c:\\farmer.txt"
, weather);
}
}
public
class
Toursist
:
IObserver
{
public
void
OnWeartherChanged(
string
weather)
{
System.IO.
File
.AppendAllText(
"c:\\tourist.txt"
, weather);
}
}
public
class
OfficeWorker
:
IObserver
{
public
void
OnWeartherChanged(
string
weather)
{
System.IO.
File
.AppendAllText(
"c:\\officeWorker.txt"
, weather);
}
}
我们可以看到,
委托
WeartherChangedHandle
类似于
IObserver接口
public delegate void WeartherChangedHandle(string wearther);
即声明了一个返回值为Void 参数为一个String类型的方法的接口。
事件
WearcherChanged类似于一个循环执行的数组
List<IObserver>
。
public event WeartherChangedHandle WearcherChanged;
即声明了一个可以执行所有
WeartherChangedHandle类型
方法的方法
public void onWearcherChanged(string wearther)
则真正触发此方法。
最后总结观察者模式的优缺点:
观察者模式的效果有以下几个优点:
(1)观察者模式在被观察者和观察者之间建立一个抽象的耦合。被观察者角色所知道的只是一个具体现察者聚集,每一个具体现察者都符合一个抽象观察者的接口。被观察者并不认识任何一个具体观察者,它只知道它们都有一个共同的接口。由于被观察者和观察者没有紧密地耦合在一起,因此它们可以属于不同的抽象化层次。
(2)观察者模式支持广播通信。被观察者会向所有的登记过的观察者发出通知。
观察者模式有下面的一些缺点:
(1)如果一个被观察者对象有很多直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
(2)如果在被观察者之间有循环依赖的话,被观察者会触发它们之间进行循环调用,导致系统崩溃。在使用观察考模式时要特别注意这一点。
(3)如果对观察者的通知是通过另外的线程进行异步投递的话,系统必须保证投递是以自恰的方式进行的。
(4)虽然观察者模式可以随时使观察者知道所观察的对象发生了变化,但是观察者模式没有相应的机制使观察者知道所观察的对象是怎么发生变化的。