观察者模式(Observer)
本质:触发联动
观察者模式又叫做:
- 发布-订阅(Publish/Subscribe)模式
- 模型-视图(Model/View)模式
- 源-监听器(Source/Listener)模式
- 从属者(Dependents)模式。
观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态上发生变化时,会通知所有观察者对象,使它们能够自动更新自己。
抽象主题(Subject)角色:
- 一个目标可以被多个观察者观察
- 目标提供对观察者注册和退订的维护
- 当目标的状态发生变化时,目标负责通知所有注册的、有效地观察者
抽象观察者(Observer)角色:
- 为所有的具体观察者定义一个接口,在得到主题的通知时更新自己。这个接口叫做更新接口。
- 抽象观察者角色一般用一个抽象类或者一个接口实现。在这个示意性的实现中,更新接口只包含一个方法(即Update()方法),这个方法叫做更新方法。
具体主题(ConcreteSubject)角色:
将有关状态存入具体现察者对象;在具体主题的内部状态改变时,给所有登记过的观察者发出通知。具体主题角色又叫做具体被观察者角色(Concrete bservable)。
具体主题角色通常用一个具体子类实现
具体观察者(ConcreteObserver)角色:
具体现察者角色实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协调。如果需要,具体现察者角色可以保存一个指向具体主题对象的引用。具体观察者角色通常用一个具体子类实现
理解
- 目标和观察者之间的关系:典型的一对多的关系:
- 单向依赖: 观察者依赖于目标
- 触发通知的时机: 完成了状态维护后触发
- 相互观察时要注意死循环
若 A、B观察C,B、C观察A,则要两套观察模式,否则死循环 - 通知的顺序
多个观察者之间的功能是平行的,相互不应该有先后的依赖关系(虽然是循环,但几乎是一瞬间同时完成的)
优缺点
优点:
- 观察者模式实现了观察者和目标之间的抽象耦合。
- 观察者模式实现了动态联动
- 观察者模式支持广播通信。被观察者会向所有的登记过的观察者发出通知。
缺点:
- 可能会引起无谓的操作—— 由于采用广播方式,不管观察者需不需要,每个观察者都会被调用update方法
例子——办公室老板来啦
办公时间做与工作无关的事情, 在老板到来时,前台负责通知好友进入工作状态
减少对象之间的耦合有利于系统的复用,但是同时设计师需要使这些低耦合度的对象之间能够维持行动的协调一致,保证高度的协作(Collaboration)。观察者模式是满足这一要求的各种设计方案中最重要的一种。
因此注意开放-封闭原则,依赖倒转原则,降低耦合性。
interface Subject
{
void Attach(Observer observer);
void Detach(Observer observer);
void Notify();
string SubjectState
{
get;
set;
}
}
class Secretary : Subject
{
//同事列表
private IList<Observer> observers = new List<Observer>();
private string action;
//增加
public void Attach(Observer observer)
{
observers.Add(observer);
}
//减少
public void Detach(Observer observer)
{
observers.Remove(observer);
}
//通知
public void Notify()
{
foreach (Observer o in observers)
o.Update();
}
//前台状态
public string SubjectState
{
get { return action; }
set { action = value; }
}
}
abstract class Observer
{
protected string name;
protected Subject sub;
public Observer(string name, Subject sub)
{
this.name = name;
this.sub = sub;
}
public abstract void Update();
}
class StockObserver : Observer
{
public StockObserver(string name, Subject sub)
: base(name, sub)
{}
public override void Update()
{
Console.WriteLine("{0} {1} 关闭股票行情,继续工作!",sub.SubjectState, name);
}
}
class NBAObserver : Observer
{
public NBAObserver(string name, Subject sub)
: base(name, sub)
{}
public override void Update()
{
Console.WriteLine("{0} {1} 关闭NBA直播,继续工作!", sub.SubjectState, name);
}
}
class Program
{
static void Main(string[] args)
{
Secretary tongzizhe = new Secretary();
StockObserver tongshi1 = new StockObserver("魏关姹",
tongzizhe);
NBAObserver tongshi2 = new NBAObserver("易管查", tongzizhe);
tongzizhe.Attach(tongshi1);
tongzizhe.Attach(tongshi2);
tongzizhe.SecretaryState = "老板回来了!";
tongzizhe.Notify();
Console.Read();
}
}
例子——投资者与股票
C#
// "Subject"
abstract class Stock
{
// Fields
protected string symbol;
protected double price;
private ArrayList investors = new ArrayList();
// Constructor
public Stock( string symbol, double price )
{
this.symbol = symbol;
this.price = price;
}
// Methods
public void Attach( Investor investor )
{
investors.Add( investor );
}
public void Detach( Investor investor )
{
investors.Remove( investor );
}
public void Notify()
{
foreach( Investor i in investors )
i.Update( this );
}
// Properties
public double Price
{
get{ return price; }
set
{
price = value;
Notify();
}
}
public string Symbol
{
get{ return symbol; }
set{ symbol = value; }
}
}
// "ConcreteSubject"
class IBM : Stock
{
// Constructor
public IBM( string symbol, double price )
: base( symbol, price ) {}
}
// "Observer"
interface IInvestor
{
// Methods
void Update( Stock stock );
}
// "ConcreteObserver"
class Investor : IInvestor
{
// Fields
private string name;
private string observerState;
private Stock stock;
// Constructors
public Investor( string name )
{
this.name = name;
}
// Methods
public void Update( Stock stock )
{
Console.WriteLine( "Notified investor {0} of {1}'s change to {2:C}",
name, stock.Symbol, stock.Price );
}
// Properties
public Stock Stock
{
get{ return stock; }
set{ stock = value; }
}
}
//Client
public class ObserverApp
{
public static void Main( string[] args )
{
// Create investors
Investor s = new Investor( "Sorros" );
Investor b = new Investor( "Berkshire" );
// Create IBM stock and attach investors
IBM ibm = new IBM( "IBM", 120.00 );
ibm.Attach( s );
ibm.Attach( b );
// Change price, which notifies investors
ibm.Price = 120.10;
ibm.Price = 121.00;
ibm.Price = 120.50;
ibm.Price = 120.75;
}
}
Java
package Observer;
public interface IInvestor {
void Update(Stock stock);
}
package Observer;
public class Investor implements IInvestor{
private String name;
private Stock stock;
public Investor(String name) {
this.name = name;
}
public Stock getStock() {
return stock;
}
public void setStock(Stock stock) {
this.stock = stock;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public void Update(Stock stock) {
System.out.println(name+" "+stock.symbol+" "+stock.price);
}
}
package Observer;
public class IBM extends Stock{
public IBM(String symbol, double price) {
super(symbol, price);
}
}
package Observer;
import java.util.ArrayList;
public abstract class Stock {
protected String symbol;
protected double price;
private ArrayList<Investor> investors=new ArrayList<Investor>();
public Stock(String symbol, double price) {
super();
this.symbol = symbol;
this.price = price;
}
public String getSymbol() {
return symbol;
}
public void setSymbol(String symbol) {
this.symbol = symbol;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
Notify();
}
public void Attach(Investor investor) {
investors.add(investor);
}
public void Detach(Investor investor) {
investors.remove(investor);
}
public void Notify() {
for(Investor i:investors)
i.Update(this);
}
}
package Observer;
public class Main {
public static void main(String[] args) {
Investor s = new Investor("aaa");
Investor b = new Investor("bbb");
IBM ibm = new IBM("IBM", 120);
ibm.Attach(s);
ibm.Attach(b);
ibm.setPrice(120.00);
ibm.setPrice(121.23);
ibm.setPrice(122.12);
ibm.setPrice(119.99);
}
}
观察者模式在各语言中的支持
Java
java.util.Observable,实现了大部分我们需要的目标的功能,还有一个接口Observer,其中定义了update方法,就是观察者的接口。
C#
.NET中提供了Delegate与Event机制(委托与事件),我们可以利用这种机制简化
委托与事件
委托是对函数的封装,可以当作给方法的特征指定一个名称。而事件则是委托的一种特殊形式,当发生有意义的事情时,事件对象处理通知过程。
委托对象用关键字delegate来声明, 事件对象用event关键字声明
有关更多委托和事件的知识点请跳转至【C#】委托与事件
模板
delegate void UpdateDelegate();
//Subject
class Subject
{
public event UpdateDelegate UpdateHandler;
// Methods
public void Attach( UpdateDelegate ud )
{
UpdateHandler += ud;
}
public void Detach( UpdateDelegate ud )
{
UpdateHandler -= ud;
}
public void Notify()
{
if(UpdateHandler != null)
UpdateHandler();
}
}
//ConcreteSubject
class ConcreteSubject : Subject
{
// Fields
private string subjectState;
// Properties
public string SubjectState
{
get{ return subjectState; }
set{ subjectState = value; }
}
}
// "ConcreteObserver"
class ConcreteObserver
{
private string name;
private string observerState;
private ConcreteSubject subject;
public ConcreteObserver( ConcreteSubject subject,
string name )
{
this.subject = subject;
this.name = name;
}
public void Update()
{
observerState = subject.SubjectState;
Console.WriteLine( "Observer {0}'s new state is {1}",
name, observerState );
}
public ConcreteSubject Subject
{
get { return subject; }
set { subject = value; }
}
}
class AnotherObserver
{
// Methods
public void Show()
{
Console.WriteLine("AnotherObserver got an Notification!");
}
}
public class Client
{
public static void Main(string[] args)
{
ConcreteSubject s = new ConcreteSubject();
ConcreteObserver o1 = new ConcreteObserver(s, "1");
ConcreteObserver o2 = new ConcreteObserver(s, "2");
AnotherObserver o3 = new AnotherObserver();
s.Attach(new UpdateDelegate(o1.Update));
s.Attach(new UpdateDelegate(o2.Update));
s.Attach(new UpdateDelegate(o3.Show));
s.SubjectState = "ABC";
s.Notify();
Console.WriteLine("--------------------------");
s.Detach(new UpdateDelegate(o1.Update));
s.SubjectState = "DEF";
s.Notify();
}
}
-
delegate void UpdateDelegate();
定义一个Delegate,用来规范函数结构。不管是ConcreteObserver类的Update方法还是AnotherObserver类的Show方法都符合该Delegate。这不象用Observer接口来规范必须使用Update方法那么严格。只要符合Delegate所指定的方法结构的方法都可以在后面被事件所处理。 -
public event UpdateDelegate UpdateHandler;
定义一个事件,一旦触发,可以调用一组符合UpdateDelegate规范的方法。 -
public void Attach( UpdateDelegate ud )
订阅事件。只要是一个满足UpdateDelegate的方法,就可以进行订阅操作
s.Attach(new UpdateDelegate(o1.Update));
s.Attach(new UpdateDelegate(o2.Update));
s.Attach(new UpdateDelegate(o3.Show))
例:猫和老鼠
class Cat
{
private string name;
public Cat(string name)
{
this.name = name;
}
public delegate void CatShoutEventHandler();
public event CatShoutEventHandler CatShout;
public void Shout()
{
Console.WriteLine("喵,我是{0}.", name);
if (CatShout != null)
{
CatShout();
}
}
}
class Mouse
{
private string name;
public Mouse(string name)
{
this.name = name;
}
public void Run()
{
Console.WriteLine("老猫来了,{0}快跑!", name);
}
}
static void Main(string[] args)
{
Cat cat = new Cat("Tom");
Mouse mouse1 = new Mouse("Jerry");
Mouse mouse2 = new Mouse("Jack");
cat.CatShout += new Cat.CatShoutEventHandler(mouse1.Run);
cat.CatShout += new Cat.CatShoutEventHandler(mouse2.Run);
cat.Shout();
Console.Read();
}
使用委托可以将多个方法绑定到同一个委托变量,当调用此变量时(这里用“调用”这个词,是因为此变量代表一个方法),可以依次调用所有绑定的方法。
+=是增加委托实例对象的意思, -=减少一个需要触发事件时通知的对象