一:动机(Motivation)
<1>在软件构建过程中,我们需要为某些对象建立一种“通知依赖关系” ——一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知。如果这样的依赖关系过于紧密,将使软件不能很好地抵御变化。
<2>使用面向对象技术,可以将这种依赖关系弱化,并形成一种稳定的依赖关系。从而实现软件体系结构的松耦合。
二:意图(Intent)
定义对象间的一种一对多的依赖关系,以便当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动更新。
——《设计模式》GoF
三:结构(Structure)
四:结构详解
五:生活中的例子
拍卖,每个投标人都有一个标有数字的牌子用于出价。拍卖师(主题)每次接受一个新的出价都改变了拍卖的当前价格,并且广播给所有的投标人(观察者)进行新的出价。
六:实现
namespace Test
{
/// <summary>
/// Observer
/// </summary>
public abstract class 竞拍人
{
//Update()
public abstract void 出价();
}
/// <summary>
/// Subject
/// </summary>
public abstract class 拍卖
{
protected System.Collections.ArrayList observers = new System.Collections.ArrayList();
//Attach
public void 注册竞拍人(竞拍人 person)
{
this.observers.Add(person);
Console.WriteLine("---{0}加入拍卖。", person);
}
//Detach
public void 注销竞拍人(竞拍人 person)
{
this.observers.Remove(person);
Console.WriteLine("---{0}离开拍卖", person);
}
//Notify广播
public virtual void 报价()
{
for (int i = this.observers.Count - 1; i >= 0; i--)
{
((竞拍人)this.observers[i]).出价();
}
}
public abstract bool 结束 { get; }
}
/// <summary>
/// ConcreteSubject
/// </summary>
public class 艺术品拍卖 : 拍卖
{
public int price;
public 艺术品拍卖(int 起拍价格)
{
this.price = 起拍价格;
}
//SubjectState
public int 当前价格
{
get { return this.price; }
set
{
if (this.price > value)
{
return;
}
this.price = value;
}
}
//Notify广播
public override void 报价()
{
base.报价();
if (base.observers.Count == 1)
{
Console.WriteLine("{0}最终胜出,最终报价为:{1}", base.observers[0], this.price);
}
else
{
if (base.observers.Count == 0)
{
Console.WriteLine("本次拍卖流拍!");
}
}
}
//SubjectState
public override bool 结束
{
get { return base.observers.Count == 1 || base.observers.Count == 0; }
}
}
/// <summary>
/// ConcreteObserver
/// </summary>
public class 艺术品竞拍人 : 竞拍人
{
//ObserverState
private int myprice;
private int topprice;
private string name;
//subject
private 艺术品拍卖 subject;
public 艺术品竞拍人(int 起始价位, int 最高心理价位, string 竞拍人姓名, 艺术品拍卖 参加的拍卖)
{
this.myprice = 起始价位;
this.topprice = 最高心理价位;
this.name = 竞拍人姓名;
this.subject = 参加的拍卖;
this.subject.注册竞拍人(this);
}
public override string ToString()
{
return this.name;
}
//Update
public override void 出价()
{
if (this.subject.当前价格 >= this.topprice)
{
Console.WriteLine("{0}退出,当前最高价格:{1},已超出最高心理价位:{2}", this.name, this.subject.当前价格, this.topprice);
this.subject.注销竞拍人(this);
}
else
{
this.myprice += 1;
this.subject.当前价格 = myprice;
if (this.myprice >= this.subject.当前价格)
{
Console.WriteLine("{0}报出新的最高价格:{1}", this.name, this.myprice);
}
}
}
}
internal class Program
{
static void Main(string[] args)
{
艺术品拍卖 subject = new 艺术品拍卖(8);
竞拍人 observer1 = new 艺术品竞拍人(8, 14, "比尔盖茨", subject);
竞拍人 observer2 = new 艺术品竞拍人(8, 12, "布什", subject);
竞拍人 observer3 = new 艺术品竞拍人(8, 10, "普京", subject);
竞拍人 observer4 = new 艺术品竞拍人(8, 10, "萨达姆", subject);
int n = 1;
while (!subject.结束)
{
Console.WriteLine("========第{0}轮报价=========", n);
subject.报价();
n++;
}
Console.ReadLine();
}
}
}
七:实现结果
八:实现要点
<1>Observer依赖Subject;
<2>Subject需要维护多个Observer;
<3>Observer的状态依赖于Subject的状态。
九:警察抓罪犯代码
namespace Test
{
/// <summary>
/// 关注的目标
/// </summary>
public abstract class Subject
{
public List<Observer> observers = new List<Observer>();
public void Attach(Observer observer)
{
observers.Add(observer);
}
public void Detach(Observer observer)
{
observers.Remove(observer);
}
public void NotifyAllObservers()
{
foreach (var observer in this.observers)
{
observer.Update();
}
}
}
/// <summary>
/// 关注目标的对象:观察者
/// </summary>
public abstract class Observer
{
//对目标的通知做出响应
public abstract void Update();
}
public class Criminal : Subject
{
private string currentLocation = "";
public string CurrentLocation
{
get { return currentLocation; }
}
public void RunAway(string location)
{
this.currentLocation = location;
base.NotifyAllObservers();
}
}
public class PoliceMan : Observer
{
private Criminal target = null;
public PoliceMan(Criminal target)
{
this.target = target;
target.Attach(this);
}
public override void Update()
{
var location = this.target.CurrentLocation;
Console.WriteLine("警察在{0}部署警力进行围堵", location);
}
}
public class Citizen : Observer
{
private Criminal target = null;
public Citizen(Criminal target)
{
this.target = target;
target.Attach(this);
}
public override void Update()
{
var location = this.target.CurrentLocation;
Console.WriteLine("热心市民:打110报警,坏人在:{0}", location);
}
}
public class Wife : Observer
{
private Criminal target = null;
public Wife(Criminal target)
{
this.target = target;
target.Attach(this);
}
public override void Update()
{
var location = this.target.CurrentLocation;
Console.WriteLine("老婆:在{0}准备接应", location);
}
}
internal class Program
{
static void Main(string[] args)
{
Subject subject = new Criminal();
Observer police = new PoliceMan(subject as Criminal);
Observer citizen = new Citizen(subject as Criminal);
Observer wife = new Wife(subject as Criminal);
Criminal criminal = subject as Criminal;
criminal.RunAway("深圳");
criminal.RunAway("广州");
Console.WriteLine();
subject.Detach(wife);
criminal.RunAway("美国");
subject.Detach(police);
Console.WriteLine();
criminal.RunAway("澳大利亚");
Console.WriteLine();
subject.Detach(citizen);
criminal.RunAway("月球");
Console.ReadLine();
}
}
}
实现结果
十:推模式与拉模式
推模式
<1>当有消息时,把消息信息以参数的形式传递(推)给所有观察者;
<2>所有的观察者得到的消息是一样的,也许有些信息对某个观察者来说根本就用不上,也就是观察者不能“按需所取”;
<3>当通知消息的参数有变化时,所有的观察者对象都要变化。
拉模式
<1>当有消息时,通知消息的方法本身并不带任何的参数,是由观察者自己到主体对象那儿取回(拉)消息;
<2>由观察者自己主动去取消息,需要什么信息,就可以取什么,不会像推模式那样得到所有的消息参数。
十一:.NET中的事件和委托
<1>利用事件和委托来实现Observer模式更加的简单和优雅,也是一种更好的解决方案;
<2>通过Observer模式,把一对多对象之间的通知依赖关系的变得更为松散,大大地提高了程序的可维护性和可扩展性,也很好的符合了开放-封闭原则。
十二:(简单)事件和委托版-猫和老鼠
namespace Test
{
/// <summary>
/// 委托,用来处理"心情改变"的方法签名
/// 猫是拉模式
/// 心情Args是推模式
/// </summary>
public delegate void 心情改变Handler(猫 sender, 心情Args e);
/// <summary>
/// 事件参数
/// </summary>
public class 心情Args
{
private bool good = false;
public bool 好心情
{
get { return good; }
set { good = value; }
}
}
public class 猫
{
private string name;
public string Name { get { return name; } }
public 猫(string name)
{
this.name = name;
}
//猫的事件,心情会改变
public event 心情改变Handler 心情改变;
public void 改变心情(bool 好不好)
{
if (this.心情改变 != null)
{
心情Args e = new 心情Args();
e.好心情 = 好不好;
this.心情改变(this, e);
}
}
}
public class 老鼠
{
private string name;
public 老鼠(string name)
{
this.name = name;
}
public override string ToString()
{
return this.name;
}
public void 应急处理(猫 cat, 心情Args 心情参数)
{
if (心情参数.好心情)
{
Console.WriteLine("{0}:\t今天{1}的心情不错,不用担心.", this, cat.Name);
}
else
{
Console.WriteLine("{0}:\t警报警报,{1}今天心情大坏,逃命要紧.", this, cat.Name);
}
}
}
internal class Program
{
static void Main(string[] args)
{
猫 tom = new 猫("tom");
老鼠 jerry = new 老鼠("jerry");
老鼠 jack = new 老鼠("jack");
老鼠 steve = new 老鼠("steve");
tom.心情改变 += new 心情改变Handler(jerry.应急处理);
tom.心情改变 += new 心情改变Handler(jack.应急处理);
心情改变Handler steveHandler = new 心情改变Handler(steve.应急处理);
tom.心情改变 += steveHandler;
tom.改变心情(true);
tom.改变心情(false);
tom.心情改变 -= steveHandler; //steve不再监听tom 的 心情改变 事件
Console.WriteLine("-------------steve不再监听tom 的 心情改变 事件 ----------");
tom.改变心情(true);
tom.改变心情(false);
Console.ReadLine();
}
}
}
实现结果
十三:实现要点
<1>本例:猫和老鼠:
只有单向监听:即,老鼠监听猫的事件;
只有单向广播:即,只有猫向老鼠广播(激发事件)。
<2>+= 可以实现注册监听。
<3>-= 可以实现注销监听。
十四:事件和委托版-竞拍
namespace Test
{
/// <summary>
/// 竞拍的事件,拍卖和竞拍人都会根据此事件参数进行判断
/// </summary>
public class 竞拍EventArgs : EventArgs
{
private int price;
public int 价格
{
get { return this.price; }
set { this.price = value; }
}
}
public class 拍卖
{
public int price;
public 拍卖(int 起拍价格)
{
this.price = 起拍价格;
}
public int 当前价格
{
get { return this.price; }
}
//当前最高出价的竞拍人
private 竞拍人 winner = null;
//专门处理竞拍人出价的事件,类似于对出价做出响应
public void 出价Handler(object sender, EventArgs e)
{
竞拍EventArgs args = e as 竞拍EventArgs;
//如果竞排人的价格高于当前的价格
if (args.价格 >= this.price)
{
this.price = args.价格; //更新当前的价格为最高的
竞拍人 person = sender as 竞拍人; //把事件的sender转化为竞拍人
this.winner = person; //当前出价最高的人是事件的sender(激发人)
Console.WriteLine("{0}提出新价格:{1}", person, args.价格);
}
}
//会发出事件:报价(广播)
public event EventHandler 报价 = null;
//是否结束
public bool 结束
{
get
{
//如果已有最高出价者,并且,只有一个人在报价,此人便胜出,拍卖结束
if (winner != null && this.报价.GetInvocationList().Length == 1)
{
Console.WriteLine("拍卖结束,获胜者为:{0},最高价格为:{1}", this.winner, this.price);
return true;
}
//如果没有人参与报价(没有人对拍卖关注),则流拍,此时,拍卖结束
if (this.报价 == null)
{
Console.WriteLine("本次拍卖流拍.");
return true;
}
else
{
//既然没有结束,就激发事件:报价,同时,把竞拍参数传出
竞拍EventArgs e = new 竞拍EventArgs();
e.价格 = this.price;
this.报价(this, e);
return false;
}
}
}
}
public class 竞拍人
{
private int myprice;
private int topprice;
private string name;
public 竞拍人(string 竞拍人姓名, int 起始价位, int 最高心理价位)
{
this.myprice = 起始价位;
this.topprice = 最高心理价位;
this.name = 竞拍人姓名;
}
public override string ToString()
{
return this.name;
}
//竞拍人会对拍卖的出价事件做出响应,引处为事件:出价
public event EventHandler 出价 = null;
//专门处理拍卖出价事件的方法
public void 报价Handler(object sender, EventArgs e)
{
竞拍EventArgs args = e as 竞拍EventArgs;
//如果拍卖的最高价格还不高于自己的最高价,继续出价
if (args.价格 <= this.topprice - 2)
{
this.myprice += 2; //这里写成每次+2元
args.价格 = this.myprice;
this.出价(this, args); //激发事件,出价
}
else
{
//把sender转化为拍卖对象
拍卖 subject = sender as 拍卖;
//在拍卖中取消自己对拍卖的报价事件的处理,即取消监听
subject.报价 -= new EventHandler(this.报价Handler);
}
}
}
internal class Program
{
static void Main(string[] args)
{
拍卖 subject = new 拍卖(8);
竞拍人 observer1 = new 竞拍人("比尔盖茨", 8, 15);
竞拍人 observer2 = new 竞拍人("布什", 8, 16);
竞拍人 observer3 = new 竞拍人("普京", 8, 14);
竞拍人 observer4 = new 竞拍人("萨达姆", 8, 13);
subject.报价 += new EventHandler(observer1.报价Handler);
subject.报价 += new EventHandler(observer2.报价Handler);
subject.报价 += new EventHandler(observer3.报价Handler);
subject.报价 += new EventHandler(observer4.报价Handler);
observer1.出价 += new EventHandler(subject.出价Handler);
observer2.出价 += new EventHandler(subject.出价Handler);
observer3.出价 += new EventHandler(subject.出价Handler);
observer4.出价 += new EventHandler(subject.出价Handler);
int n = 1;
Console.WriteLine("========第{0}轮报价=========", n);
while (!subject.结束)
{
n++;
Console.WriteLine("========第{0}轮报价=========", n);
}
Console.ReadLine();
}
}
}
实现结果
十五:实现要点
<1>本例:事件和委托版拍卖略为复杂:
拍卖与竞拍人之间相互监听
拍卖监听竞拍人的出价事件
竞拍人监听拍卖的报价事件
这是一种双向监听
<2>更多时候,使用单向监听,即:单向广播。
十六:适用性
<1>需要实现发布-订阅模型时
<2>需要实现广播机制时
十七:总结
<1>使用面向对象的抽象,Observer模式使得我们可以独立地改变目标与观察者,从而使二者之间的依赖关系达致松耦合。
<2>目标发送通知时,无需指定观察者,通知(可以携带通知信息作为参数)会自动传播。观察者自己决定是否需要订阅通知,目标对象对此一无所知。
<3>在C#的event中,委托充当了抽象的Observer接口,而提供事件的对象充当了目标对象。委托是比抽象Observer接口更为松耦合的设计。