Fork me on GitHub

设计模式系列之观察者模式

模式说明

观察者模式是作者接触的比较早的设计模式,也是作者比较早理解的设计模式。该模式属与行为模式。该模式也叫依赖(Dependents),发布-订阅(publish-Subscribe)

意图

定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

GOF,设计模式:可复用面向对象软件的基础

别名

依赖(Dependents),发布-订阅(publish-Subscribe)

适用性

  • 当一个抽象模型有两个方面,其中一个方面依赖于另一方面。将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。【这条博主也不是很理解。】
  • 当对一个对象的改变需要同时改变其他对象,而不知道具体有多少对象有待改变。
  • 当一个对象必须通知其他对象。而它又不能假定其他对象是谁。换言之,你不希望这些对象是紧密耦合的。

结构

观察者模式UML图

参与者

  • Subject(目标)

    • 目标知道它的观察者,可以有人以多个个观察者观察同一个目标。
    • 提供注册和删除观察者对象的接口
  • Observer(观察者)

    • 为那些在目标发生改变时需要获取通知的对象定义一个接口。
  • ConcreteSubject(具体的目标)

    • 将有关状态存入各ConcreteObserver对象
    • 当它的状态发生改变,向他的各个观察者发出通知。
  • ConcreteObserver

    • 维护一个指向ConcreteSubject对象的引用。
    • 存储有关状态,这些状态应与目标对象的状态保持一致。
    • 实现Observer的更新接口以使自身的状态与目标的状态保持一致。

模式实战

小张开了一家饭店。饭店的菜单会经常发生变化,菜单变化的时候,小张要通知采购部的相关人员。对菜品进行采购。当然最好通知经常来吃饭的客户。

这个场景非常符合我们观察者模式,当菜单变化的时候要通知客户和采购部门。观察者的数量不确定,类型也不确定。将来可能也会有其他类型的观察者。

我们先定义主题接口和具体的饭店菜单主题

1
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
public interface ISubject
{
void Attach(IObserver observer);
void Detach(IObserver observer);
void Notify();
}
public class Restaurant : ISubject
{
public List<IObserver> observers = new List<IObserver>();
public string Name { get; set; }
public string Menus { get; set; }
public void Attach(IObserver observer)
{
observers.Add(observer);
}
public void Detach(IObserver observer)
{
observers.Remove(observer);
}
public void Notify()
{
foreach (var observer in this.observers)
{
observer.Update(Menus);
}
}
}

然后我们定义具体观察者接口和相关的观察者

1
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
public interface IObserver
{
void Update(string Menu);
}
public class Customer : IObserver
{
public Customer(string name, TextBox txtlog)
{
this.Name = name;
this.Log = txtlog;
}
public string Name { get; set; }
public TextBox Log { get; set; }
public void Update(string Menu)
{
//throw new NotImplementedException();
Log.AppendText(string.Format("{0}:我得到消息菜价变化了{1}", this.Name,System.Environment.NewLine));
}
public override string ToString()
{
return Name;
}
}
public class Buyer : IObserver
{
public Buyer(string name, TextBox txtlog)
{
this.Name = name;
this.Log = txtlog;
}
public string Name { get; set; }
public TextBox Log { get; set; }
public void Update(string Menu)
{
//throw new NotImplementedException();
Log.AppendText(string.Format("{0}:我得到了消息菜单变化了。马上采购去{1}", this.Name, System.Environment.NewLine));
}
public override string ToString()
{
return Name;
}
}

创建WINFROM项目为实现客户端代码。当我们改变菜单,点击确定按钮时。所有的观察者都得到了更新。

1
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
public partial class MainFrom : Form
{
public MainFrom()
{
InitializeComponent();
}
public ISubject Subject;
private void MainFrom_Load(object sender, EventArgs e)
{
Restaurant restaurantSubject = new Restaurant();
restaurantSubject.Menus = @"糖醋排骨 50
油焖大虾 20
糖醋里脊 10
土豆丝 15";
this.txtMenu.Text = restaurantSubject.Menus;
Subject = restaurantSubject;
Customer customer = new Customer("客户小张", this.txtLog);
Customer customer2 = new Customer("客户小李", this.txtLog);
Buyer buyer = new Buyer("采购小王", this.txtLog);
lbxObserver.Items.Add(customer);
lbxObserver.Items.Add(customer2);
lbxObserver.Items.Add(buyer);
Subject.Attach(customer);
Subject.Attach(customer2);
Subject.Attach(buyer);
}
private void btnChangeMenus_Click(object sender, EventArgs e)
{
((Restaurant)Subject).Menus = this.txtMenu.Text;
Subject.Notify();
}
private void btnAddObserver_Click(object sender, EventArgs e)
{
if (this.cbxObserverType.Text == "客户")
{
IObserver observer = new Customer("客户" + this.txtName.Text, this.txtLog);
lbxObserver.Items.Add(observer);
Subject.Attach(observer);
}
else if (this.cbxObserverType.Text == "采购")
{
IObserver observer = new Buyer("采购" + this.txtName.Text, this.txtLog);
lbxObserver.Items.Add(observer);
Subject.Attach(observer);
}
}
private void btnDelObserver_Click(object sender, EventArgs e)
{
IObserver item = lbxObserver.SelectedItem as IObserver;
lbxObserver.Items.Remove(item);
Subject.Detach(item);
}
}

程序的界面如下

程序界面

基本的功能都实现了,动态的添加、删除观察者。菜单改变,改变后用户观察者得到的消息展示。如果先查看代码传送门,做之前想要做的多少多好,每次一开始写,就写的很差劲了。多练吧。

效果及实现要点

推拉模式

推模型:目标想所有的观察者发送改变的详细信息。不管观察者是否需要。

缺点:目标知道观察者需要的信息。所以目标难以复用。

拉模型:目标不主动推送改变的数据,由观察者得到更新通知后,显示访问目标对象。

缺点:效率较差,因为目标更改了那些内容需要观察者自己去获取。

.NET EVEN与观察者模式接口

  • 在C#语言中从语言层面已经实现了观察者模式,那就是事件和委托。而且事件和委托比观察者的模型更灵活。
  • System.IObservable.aspx) 和 System.IObserver.aspx) 接口 ,.NET4.0自带了对观察者实现的泛型接口。提供的接口与传统的观察者模不同,但是目的是一样的。传送门.aspx)

显示的制定感兴趣的改变

当只对主题的特定数据和事件感兴趣的时候,可以注册对主题感兴趣的方面(aspects),主题在执行更新操作的时候将这一方面的改变发送给订阅者。接口如下,目前我对方面的认识还不够,但是我认为这个是非常有意义的。当我们只对菜单的某一个菜感兴趣时或者我们只对周五菜单改变刚兴趣这种接口就比较有威力了。书中的定义如下。

1
2
3
void Subject::Attach(Observer*,Aspect& interest);
void Observer:Update(Subject*,Aspect& interest);

###

模式总结

通过Observer模式,把一对多对象之间的通知依赖关系的变得更为松散,大大地提高了程序的可维护性和可扩展性,也很好的符合了开放-封闭原则。

参考资料

MSDN WebCast 《C#面向对象设计模式纵横谈(19):Observer 观察者模式(行为型模式)》

Erich Gamma等,《设计模式:可复用面向对象软件的基础》

()

坚持原创技术分享,您的支持将鼓励我继续创作
显示 Gitment 评论