结构图
角色
- 抽象主题(Subject)角色:主题角色把所有对观察者对象的引用保存在一个聚集里,每个主题都可以有任何数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象,主题角色又叫做抽象被观察者(Observable)角色。
- 抽象观察者(ConcreteSubject)角色:为所有的具体观察者定义一个接口,在得到主题的通知时更新自己。这个接口叫做更新接口。
- 具体主题(Observer)角色:将有关状态存入具体观察者对象;在具体主题的内部状态改变时,给所有登记过的观察者发出通知。具体主题角色又叫做具体被观察者角色(Concrete Observable)。
- 具体观察者(ConcreteObserver)角色:存储与主题的状态自洽的状态。具体观察者角色事项抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协调。如果需要,具体观察者角色可以保存一个指向具体主题对象的引用。
动机
在软件构建过程中集合,某些对象的状态在转换过程中,可能由于某种需要,要求程序能够回溯到对象之前处于某个点时的状态。如果使用一些公有接口来让其他对象得到对象的状态,便会暴露对象的细节实现。
如何实现对象状态的良好保存与恢复?但同时又不会因此而破坏对象本身的封装性。
意图
定义对象间的一种一对多的依赖关系,以便当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动更新。
示意性代码
示意性代码
'MainApp test application
Module MainApp
Public Sub Main()
'Configure Observer pattern
Dim s As ConcreteSubject = New ConcreteSubject()
s.Attach(New ConcreteObserver(s, "X"))
s.Attach(New ConcreteObserver(s, "Y"))
s.Attach(New ConcreteObserver(s, "Z"))
'Change subject and notify observers
s.SubjectState = "ABC"
s.Notify()
'Wait for user
Console.ReadLine()
End Sub
End Module
' "Subject"
Public MustInherit Class Subject
Private observers As New List(Of Observer)
Public Sub Attach(ByVal observer As Observer)
observers.Add(observer)
End Sub
Public Sub Detach(ByVal observer As Observer)
observers.Remove(observer)
End Sub
Public Sub Notify()
For Each o As Observer In observers
o.update()
Next
End Sub
End Class
'"ConcreteSubject"
Public Class ConcreteSubject
Inherits Subject
Private _subjectState As String
'Property
Public Property SubjectState() As String
Get
Return _subjectState
End Get
Set(ByVal value As String)
_subjectState = value
End Set
End Property
End Class
'"Observer"
Public MustInherit Class Observer
MustOverride Sub update()
End Class
'"ConcreteObserver"
Public Class ConcreteObserver
Inherits Observer
Private _name As String
Private observerState As String
Private _subject As ConcreteSubject
'Constructor
Public Sub New(ByVal subject As ConcreteSubject, ByVal name As String)
Me._subject = subject
Me._name = name
End Sub
'Property
Public Property Subject() As ConcreteSubject
Get
Return _subject
End Get
Set(ByVal value As ConcreteSubject)
_subject = value
End Set
End Property
Public Overrides Sub Update()
observerState = _subject.SubjectState
Console.WriteLine("Observer {0}'s new state is {1}", _
_name, observerState)
End Sub
End Class
'MainApp test application
Module MainApp
Public Sub Main()
'Configure Observer pattern
Dim s As ConcreteSubject = New ConcreteSubject()
s.Attach(New ConcreteObserver(s, "X"))
s.Attach(New ConcreteObserver(s, "Y"))
s.Attach(New ConcreteObserver(s, "Z"))
'Change subject and notify observers
s.SubjectState = "ABC"
s.Notify()
'Wait for user
Console.ReadLine()
End Sub
End Module
' "Subject"
Public MustInherit Class Subject
Private observers As New List(Of Observer)
Public Sub Attach(ByVal observer As Observer)
observers.Add(observer)
End Sub
Public Sub Detach(ByVal observer As Observer)
observers.Remove(observer)
End Sub
Public Sub Notify()
For Each o As Observer In observers
o.update()
Next
End Sub
End Class
'"ConcreteSubject"
Public Class ConcreteSubject
Inherits Subject
Private _subjectState As String
'Property
Public Property SubjectState() As String
Get
Return _subjectState
End Get
Set(ByVal value As String)
_subjectState = value
End Set
End Property
End Class
'"Observer"
Public MustInherit Class Observer
MustOverride Sub update()
End Class
'"ConcreteObserver"
Public Class ConcreteObserver
Inherits Observer
Private _name As String
Private observerState As String
Private _subject As ConcreteSubject
'Constructor
Public Sub New(ByVal subject As ConcreteSubject, ByVal name As String)
Me._subject = subject
Me._name = name
End Sub
'Property
Public Property Subject() As ConcreteSubject
Get
Return _subject
End Get
Set(ByVal value As ConcreteSubject)
_subject = value
End Set
End Property
Public Overrides Sub Update()
observerState = _subject.SubjectState
Console.WriteLine("Observer {0}'s new state is {1}", _
_name, observerState)
End Sub
End Class
一个实例
下面的观察者代码演示了投资者登记后,每次股价发生变化,都会得到通知。
实例代码
Module MainApp
Public Sub MainApp()
'Create investors
Dim s As New Investor("Sorros")
Dim b As New Investor("Berkshire")
'Create IBM stock and attach investors
Dim ibm As New IBM("IBM", 120.0)
ibm.Attach(s)
ibm.Attach(b)
'Change price, which notifies investors
ibm.Price = 121.1
ibm.Price = 122.0
ibm.Price = 123.5
ibm.Price = 124.75
'Wait for user
Console.ReadLine()
End Sub
End Module
'"Subject"
Public MustInherit Class Stock
Private _Price As Double
Private _Symbol As String
Private investors As New List(Of Investor)
'Constructor
Sub New(ByVal symbol As String, ByVal price As Double)
_Symbol = symbol
_Price = price
End Sub
'Properties
Public Property Price() As Double
Get
Return _Price
End Get
Set(ByVal value As Double)
_Price = value
Notify()
End Set
End Property
Public Property Symbol() As String
Get
Return _Symbol
End Get
Set(ByVal value As String)
_Symbol = value
End Set
End Property
Public Sub Attach(ByVal investor As Investor)
investors.Add(investor)
End Sub
Public Sub Detach(ByVal investor As Investor)
investors.Remove(investor)
End Sub
Public Sub Notify()
For Each investor As Investor In investors
investor.Update(Me)
Next
Console.WriteLine("")
End Sub
End Class
'"ConcreteObject"
Public Class IBM
Inherits Stock
'Constructor
Sub New(ByVal symbol As String, ByVal price As Double)
MyBase.New(symbol, price)
End Sub
End Class
'"Observer"
Public Interface IInvestor
Sub Update(ByVal stock As Stock)
End Interface
'"ConcreteObserver"
Public Class Investor
Implements IInvestor
Private _Stock As Stock
Private name As String
'Constructor
Sub New(ByVal name As String)
Me.name = name
End Sub
Public Property Stock() As Stock
Get
Return _Stock
End Get
Set(ByVal value As Stock)
_Stock = value
End Set
End Property
Public Sub Update(ByVal stock As Stock) Implements IInvestor.Update
Console.WriteLine("Notified {0} of {1}'s " & "change to {2:C}", _
name, stock.Symbol, stock.Price)
End Sub
End Class
Module MainApp
Public Sub MainApp()
'Create investors
Dim s As New Investor("Sorros")
Dim b As New Investor("Berkshire")
'Create IBM stock and attach investors
Dim ibm As New IBM("IBM", 120.0)
ibm.Attach(s)
ibm.Attach(b)
'Change price, which notifies investors
ibm.Price = 121.1
ibm.Price = 122.0
ibm.Price = 123.5
ibm.Price = 124.75
'Wait for user
Console.ReadLine()
End Sub
End Module
'"Subject"
Public MustInherit Class Stock
Private _Price As Double
Private _Symbol As String
Private investors As New List(Of Investor)
'Constructor
Sub New(ByVal symbol As String, ByVal price As Double)
_Symbol = symbol
_Price = price
End Sub
'Properties
Public Property Price() As Double
Get
Return _Price
End Get
Set(ByVal value As Double)
_Price = value
Notify()
End Set
End Property
Public Property Symbol() As String
Get
Return _Symbol
End Get
Set(ByVal value As String)
_Symbol = value
End Set
End Property
Public Sub Attach(ByVal investor As Investor)
investors.Add(investor)
End Sub
Public Sub Detach(ByVal investor As Investor)
investors.Remove(investor)
End Sub
Public Sub Notify()
For Each investor As Investor In investors
investor.Update(Me)
Next
Console.WriteLine("")
End Sub
End Class
'"ConcreteObject"
Public Class IBM
Inherits Stock
'Constructor
Sub New(ByVal symbol As String, ByVal price As Double)
MyBase.New(symbol, price)
End Sub
End Class
'"Observer"
Public Interface IInvestor
Sub Update(ByVal stock As Stock)
End Interface
'"ConcreteObserver"
Public Class Investor
Implements IInvestor
Private _Stock As Stock
Private name As String
'Constructor
Sub New(ByVal name As String)
Me.name = name
End Sub
Public Property Stock() As Stock
Get
Return _Stock
End Get
Set(ByVal value As Stock)
_Stock = value
End Set
End Property
Public Sub Update(ByVal stock As Stock) Implements IInvestor.Update
Console.WriteLine("Notified {0} of {1}'s " & "change to {2:C}", _
name, stock.Symbol, stock.Price)
End Sub
End Class
改进后的代码
用.Net的事件委托实现上面的实例
改进后的代码
Module MainApp
Public Sub Main()
'Create investors
Dim s As New Investor("Sorros")
Dim b As New Investor("Berkshire")
'Create IBM stock and attach investors
Dim ibm As New IBM("IBM", 120.0)
ibm.Attach(s)
ibm.Attach(b)
'Change price, which notifies investors
ibm.Price = 121.1
ibm.Price = 122.0
ibm.Price = 123.5
ibm.Price = 124.75
'Wait for user
Console.ReadLine()
End Sub
End Module
'"Subject"
Public MustInherit Class Stock
Private _Price As Double
Private _Symbol As String
Private Event InvesteEvent(ByVal symbol As String, ByVal price As Double)
'Constructor
Sub New(ByVal symbol As String, ByVal price As Double)
_Symbol = symbol
_Price = price
End Sub
'Properties
Public Property Price() As Double
Get
Return _Price
End Get
Set(ByVal value As Double)
_Price = value
Notify()
End Set
End Property
Public Property Symbol() As String
Get
Return _Symbol
End Get
Set(ByVal value As String)
_Symbol = value
End Set
End Property
Public Sub Attach(ByVal investor As IInvestor)
AddHandler InvesteEvent,AddressOf investor.Update
End Sub
Public Sub Detach(ByVal investor As IInvestor)
RemoveHandler InvesteEvent,AddressOf investor.Update
End Sub
Public Sub Notify()
OnInveste
Console.WriteLine("")
End Sub
Protected Sub OnInveste()
RaiseEvent InvesteEvent(_symbol,_Price)
End Sub
End Class
'"ConcreteSbject"
Public Class IBM
Inherits Stock
'Constructor
Sub New(ByVal symbol As String, ByVal price As Double)
MyBase.New(symbol, price)
End Sub
End Class
'"Observer"
Public Interface IInvestor
Sub Update(ByVal symbol As String, ByVal price As Double)
End Interface
'"ConcreteObserver"
Public Class Investor
Private name As String
'Constructor
Sub New(ByVal name As String)
Me.name = name
End Sub
Public Sub Update(ByVal symbol As String, ByVal price As Double) Implements IInvestor.Update
Console.WriteLine("Notified {0} of {1}'s " & "change to {2:C}", _
name, Symbol, Price)
End Sub
End Class
Module MainApp
Public Sub Main()
'Create investors
Dim s As New Investor("Sorros")
Dim b As New Investor("Berkshire")
'Create IBM stock and attach investors
Dim ibm As New IBM("IBM", 120.0)
ibm.Attach(s)
ibm.Attach(b)
'Change price, which notifies investors
ibm.Price = 121.1
ibm.Price = 122.0
ibm.Price = 123.5
ibm.Price = 124.75
'Wait for user
Console.ReadLine()
End Sub
End Module
'"Subject"
Public MustInherit Class Stock
Private _Price As Double
Private _Symbol As String
Private Event InvesteEvent(ByVal symbol As String, ByVal price As Double)
'Constructor
Sub New(ByVal symbol As String, ByVal price As Double)
_Symbol = symbol
_Price = price
End Sub
'Properties
Public Property Price() As Double
Get
Return _Price
End Get
Set(ByVal value As Double)
_Price = value
Notify()
End Set
End Property
Public Property Symbol() As String
Get
Return _Symbol
End Get
Set(ByVal value As String)
_Symbol = value
End Set
End Property
Public Sub Attach(ByVal investor As IInvestor)
AddHandler InvesteEvent,AddressOf investor.Update
End Sub
Public Sub Detach(ByVal investor As IInvestor)
RemoveHandler InvesteEvent,AddressOf investor.Update
End Sub
Public Sub Notify()
OnInveste
Console.WriteLine("")
End Sub
Protected Sub OnInveste()
RaiseEvent InvesteEvent(_symbol,_Price)
End Sub
End Class
'"ConcreteSbject"
Public Class IBM
Inherits Stock
'Constructor
Sub New(ByVal symbol As String, ByVal price As Double)
MyBase.New(symbol, price)
End Sub
End Class
'"Observer"
Public Interface IInvestor
Sub Update(ByVal symbol As String, ByVal price As Double)
End Interface
'"ConcreteObserver"
Public Class Investor
Private name As String
'Constructor
Sub New(ByVal name As String)
Me.name = name
End Sub
Public Sub Update(ByVal symbol As String, ByVal price As Double) Implements IInvestor.Update
Console.WriteLine("Notified {0} of {1}'s " & "change to {2:C}", _
name, Symbol, Price)
End Sub
End Class
Observer Pattern模式的几个要点:
1、使用面向对象的抽象,Observer模式使得我们可以独立地改变目标与观察者,从而使二者之间的依赖关系达致松耦合。
2、目标发送通知时,无需指定观察者,通知(可以携带通知信息作为参数)会自动传播。观察者自己决定是不需要订阅通知,目标对象对此一无所知。
3、在C#的event中,委托充当了抽象的Observer接口,而提供事件的对象充当了目标对象。委托是比抽象Observer更为松耦合的设计。
我的理解
封装对象通知,支持通信对象的变化。
参考资料
《C#面向对象设计模式纵横谈系列课程(21)》 李建中老师