有关Observer的一篇翻译文章,不当处请指正。 (20分)

  • 主题发起人 delphikj
  • 开始时间
D

delphikj

Unregistered / Unconfirmed
GUEST, unregistred user!
Observer
One exciting development in programming over the past few years has been
the emergence of designpatterns as a way of describing solutions to common
software problems. In a nutshell, a pattern is a problem, the context of the
problem and a solution all bundled up and ready to use. The classic book on
design patterns is 'Design Patterns' by Gamma, Helm, Johnson and Vlissides,
which describes overtwenty design patterns. Every pattern is given a simple,
memorable name to help designers communicate their ideas more easily when
using patterns.
在过去几年中,软件业一个令人瞩目的进展就是出现了作为描述一般软件问题解决方案
的设计模式。本质上讲,一个模式就是一个问题,问题的上下联系与一个解决方法的整体
捆绑单元。在由Gamma, Helm, Johnson 与 Vlissides合著的经典《设计模式》一书中
提出了二十种模式,而且给每种模式起了一个简单、易记的名字,以帮助设计者在使用
模式时更易于将思想联系为一体。
In this article I want to describe the Observer pattern and show how it can be
implemented in Delphi, as well as explore some powerful Delphi programming
techniques based on using interfaces. The problem that the Observer pattern
solves is that of defining a one to many relationship between objects such
that when one object changes state, many other objects are notified and updated
automatically. The context is that of having many different objects react to
changes in one other object. The solution is of course the observer pattern.
Borland use this pattern at various places within the Delphi VCL, but did not
make it explicit or wrap it up in its own classes. For example, when an object
of class TComponent or one of its descendents is about to be deleted, it sends
a FreeNotification message to all other components to tell them it is about to
disappear so that they can remove links to it.
在这篇文章中,我想描述一下observer模式,并看看怎样在Delphi中使用它,还要介绍基
于使用interfaces强大的Delphi编程技术。observer模式所解决的问题是:在objects之间
设定一个一对多的(模式),以使当其中一个object状态改变时,其他的objects会被通知
到,而且自动作出反应。联系在于多个不同的objects对一个object的改变产生响应。这种
过程的解决(模式)被称为observer模式。Borland 在Delphi VCL 中不同地方都用了这种
模式,但并没有将其模式外在化或封装在类中。例如,当一个Tcomponent或其子类的一个
Object将被消除时,它发出一个FreeNotification消息给所有其他的components,告诉它
们这个(Object)将消失,这样这些components就开始取消于该(object)的联系。
Most design patterns are described in the literature using a mixture of class
diagrams and text, and the Observer pattern is no exception. A copy of the
class diagram used to show the Observer Pattern is
和大多的设计模式一样,observer模式也采用类图和文字的表述方式。
图一就是observer模式的
类图表示。
shown in figure 1.
Figure 1: Observer Pattern (UML notation)
|------------| |------------|
| supject | 1..* |observer |
| {abstract} |------------------>|{abstract} |
|------------| observers |____________|
| Attach | | |
| (observer) | | |
| Detach | |------------|
|(observer) | | Update |
| Notify | --------------
-------------| ^
^ |
| |
| |
| |----------------|
|---------------| | |
|ConcreteSubject| subject |concreteobserver|
| Subjectstate |<--------------|----------------|
| GetState | | observerstate |
| setState | |----------------|
----------------- |Update |
(图1) -----------------

The basis of the Observer Pattern is the relation between the abstract classes
Subject and Observer. TheSubject knows its observers and provides an interface
for attaching and detaching Observer objects. Subjects use the Update
interface defined by the Observer class to notify Observers of changes in the
Subject. ConcreteSubject is a class that implements the Subject interface and
provides the stateinformation we are interested in. ConcreteObserver implements
the Observer interface, maintaining a link to the ConcreteSubject that it is
observing.
Observer模式的基础在于abstract classes Subject 与Observer之间的关系。该Subject知
道它的observers并提供一个与Observer objects联系、解除联系的interface。Subjects通
过由Observer class 定义的interface 去通知别的Observers在subject上发生的改变。
ConcreteSubject是使用Subject Interface 并提供我们感兴趣状态信息的类。该类使用
Observer interface,负责它正在观察的与它的链接。
Interfaces
Before progressing any further, it might be worthwhile looking at the Delphi
method of handling abstract classes. Borland introduced a new use for the
interface keyword in Delphi 3, using it to define an interface type. An
interface type provides a way of grouping together a description of some
functionality without actually specifying the implementation details. You
implement the functionality by adding the name of the interface type to the
definition of a new class, effectively inheriting the properties and method
declarations of the interface. Interfaces pay a major part in the mechanism for
dealing with COM objects within Delphi 3, but thedo
cumentationdo
es not make
it clear that interfaces are also useful in general non-COM programming.
在着手进一步编程之前,我们有必要看看Delphi方法中对abstract classes的处理。
在Delphi 3中Boland引入了interface的关键字,用它来定义interface类型。interface类
型提供了一种对多种功能进行组织的方式, 同时又不需我们了解功能具体实现的细节。你
可以通过添加interface类型名到一个新定义的类来实现你的功能,这种新类有效地继承了
在interface中声明了的Properties与method 。Delphi 3中Interface在处理COM objects
机制中扮演了一个 重要的角色,但在一般的非COM编程中,Interface并没有显得有用。
The declaration of an interface is very similar to that of a class. Every
interface has a name, which by convention begin
s with 'I', and contains the
declaration of the interfaces procedures and functions all of which have public
visibility. You can also declare properties, specifying read and write method
names rather than the name of a private variable. You can specify an ancestor
interface by putting the ancestor name in brackets after the interface keyword.
Interface的声明与类的声明非常类似。每个Interface有一个名字(约定以I开头),另外
还包括interfaces,procedures与functions的声明(可见性为public)。你也可以定义
properties,特殊的read,write方法名而不是私有变量名。你可以通过将祖先Interface名
放在关键字Interface 后括号内的方式来指定祖先interface。
The Observer and Subject interfaces are declared in figure 2. I have prefixed
all the method names with 'Obs' to avoid name collisions with the VCL and added
a ObsFreeNotification method which is used to tell Observer objects that the
Subject is about to be Freed. ISubject is forward declared because the Observer
pattern requires that Observers know about subjects and that Subjects also
know about their Observers.
我们在在图2中声明了Observer与Subject interfaces。为避免与VCL冲突,我将方法名加上
前缀‘obs’,并加了一个ObsFreeNotification method来通知Observer objects解除
subject的信息。由于observer模式要求observers知道subjects,并且subjects同样它们
的Observers,所以Isubject被向前声明。
An object implementing the IObserver interface calls the ObsAttach method of
an object implementing the ISubject interface to register the fact that it
wants to receive notification of changes. When the object implementing ISubject
changes, it calls the ObsUpdate method of every IObserver to inform them of
the change.
一个正在使用Iobserver interface的object 寻找一个使用Isubject interface 的object
中ObsAttach方法来注册,事实上它(前者)想接收到(后者)变化的通知。当使用
Isubject interface 的object发生变化时,它通过每一个IObserver 中 ObsUpdate
method 来通知它们自己的变化。
Listing 1: Declaration of the IObserver and ISubject interfaces
Iobserver 与 ISubject interfaces 的声明
type
ISubject = Interface;
// forward
// IObserver defines an Observer class as defined in G4:Observer.

IObserver = interface
procedure ObsUpdate (Subject : ISubject);
//与Isubject 联系的纽带
procedure ObsFreeNotification (Subject : ISubject);
procedure ObsSetSubject (Subject : ISubject);
end;

// ISubject defines the Subject class as defined in G4:Observer.
ISubject = Interface
procedure ObsAttach (Observer : IObserver);
//与Iobserver联系的纽带
procedure ObsDetach (Observer : IObserver);
procedure ObsNotify;
procedure ObsFreeNotify;
// 解除信息
end;
Now that the abstract side has been dealt with, we can turn to producing a
useful implementation of Observer and use it to add flexibility to a simple
Delphi program for calculating VAT.
抽象的一面现已处理完毕,下面我们转入一个有用的observer的具体应用,并使用它来
为一个简单的Delphi计算VAT(增值税) 程序添加灵活性。
In a non-Observer based VAT calculator, you might write code like
Edit2.Text := FloatToStr(StrToFloat(Edit1.Text)*0.175);
in the Change
event handler of Edit1. This limits the functionality of the Change event
to justdo
ing the calculation and nothing else
. You cannot easily add new
functionality to this at run time - for example calculating the total amount
including VAT or showing the result on another form。
在非observer VAT计算器中,你也许会在反应Edit1事件变化句柄中这样写
Edit2.Text :=FloatToStr(StrToFloat(Edit1.Text)*0.175);这样做仅仅是作了个计算而
已。你并不容易能在实时计算时加入新的功能,例如计算包括VTA的总数目或者显示另一个
窗体的结果。
Using Observer as a mechanism for linking Delphi form components together
offers a better solution. Imagine that instead of coding the calculation
into the Change event, we called the obsNotify method of a component
implementing the ISubject interface. This would then
notify all
observers (including an observer that handled our VAT calculation) that
the change had occurred. We could add and remove observers
at run time without making any code changes. Since we want this solution to
work with Delphi's forms, a sensible place to start is with TComponent and
extend it to support the IObserver and ISubject interfaces.
使用observers来链接Delphi窗体组件的机制提供了一种更好的解决方案。设想一下,
如果我们不是在Change event中编计算代码,而是call 一个使用ISubject interface的
组件obsNotify method来实现的情况。改变将被通知给每个Observers(包括控制VAT计算
器的observer),我们可以实现实时添加或减少observers而不用改变编码。既然我们
想以Delphi窗体工作的形式解决,那么就要从Tcomponent出发,并将其扩大以支
持Iobserver与Isubject interfaces.

Implementing the concrete classes(Concrete classes 的使用)
TComponent contains the plumbing for interfaces but you need to provide an
implementation of the COM IUnknown interface before it actually works,
illustrating just how closely interfaces and COM are linked within the VCL.
IUnknown specifies three methods, _AddRef, _Release and QueryInterface. The
first two are used to keep track of how many references have been made to a
given interface, while QueryInterface is used to determine if a given COM
object actually supports a specific interface. Within TComponent, Borland
delegates the implementation of the IUnknown methods to an object specified
using the VCLComObject property. The snag is that Borland have flagged this
property as 'for internal use only', so we need to find another way. The
easiest approach is to add an IUnknown interface and implementation to
a TComponent and use that as the ancestor for our TObserverComponent and
TSubjectComponent classes. TInterfacedComponent is shown in listing 1 below.
Tcomponent中包含了对interfaces的封装,但在它实际工作前,你需要提供一个COM
Iunknown Interface 的使用,就象在VCL中 COM与interfaces链接的方式一样。
Iunknown 提供了三种方法,_AddRef, _Release 和 QueryInterface.前两种用来记录有
多少references已被用在给定的interface,QueryInterface用来确定是否一个给定的
COM Object支持一个特定的Interface。在Tcomponent内,Boland将Iunknown methods
的使用授权给特定的使用VCLComObject property的object。麻烦在于Boland将这Property
标记为‘仅在内部使用’,所以我们需要去寻找另一种方法。最方便的捷径 是向
Tcomponent加一个Iunknown Interface 和 implemention , 并以它作为
TObserverComponent 与 TSubjectComponent classes 的父类。
TinterfacedComponent清单所示。

Listing 2: TInterfacedComponent, a base class for interfaced components
type
TInterfacedComponent = class (TComponent, IUnknown)
private
FRefCount : integer;
public
function _AddRef: Integer;
stdcall;
function _Release: Integer;
stdcall;
function QueryInterface(const IID: TGUID;
out Obj): Integer;
stdcall;
end;

implementation
{TInterfacedComponent}
function TInterfacedComponent._AddRef: Integer;
begin
Inc(FRefCount);
Result := FRefCount;
end;

function TInterfacedComponent._Release: Integer;
begin
Dec(FRefCount);
Result := FRefCount;
end;

function TInterfacedComponent.QueryInterface(const IID: TGUID;
out Obj): Integer;
const
E_NOINTERFACE = $80004002;
begin
if GetInterface(IID, Obj) then
Result := 0 else
Result := E_NOINTERFACE;
end;
The code for the methods of TInterfacedComponent is 'borrowed' from the
implementation of TInterfacedObject, which provides a base class for building
interfaced objects from. If you check the code for TInterfacedObject._Release,
you will see that I have removed some redundant functionality - descendents
of TInterfacedObject are destroyed when their reference count reaches zero.
Because TInterfacedComponent objects are destroyed when their owner is
destroyed, this auto-destruct behaviour is not required.
Our TObserverComponent and TSubjectComponent classes are shown in listing 3.
TinterfacedComponent方法的编码是从TinterfacedObject的Implemention中“借”来的,
TinterfacedObject提供了建立interfaced objects的基类。如果你查看
TInterfacedObject._Release中的编码,你会看到我已将一些多余的派生功能从
TinterfacedObject(该类当它们的Reference count 到0时被消除)中去除了。
由于TInterfacedComponent objects在它们的owner 被销毁时也被销毁,
这个自动销毁特性就不需要了。我们的TObserverComponent 与 TSubjectComponent
classes如清单3所示。
Listing 3: TobserverComponent and TsubjectComponent: implementation of the Observer pattern
type
TObsEvent = procedure (Observer : IObserver) of object;
TObserverComponent = class (TInterfacedComponent, IObserver)
private
FSubject : ISubject;
FOnObsUpdate : TObsEvent;
public
procedure ObsUpdate (Subject : ISubject);
procedure ObsFreeNotification (Subject : ISubject);
procedure ObsSetSubject (Subject : ISubject);
published
property OnObsUpdate : TObsEvent read FOnObsUpdate write FOnObsUpdate;
end;

TSubjectComponent = class(TInterfacedComponent, ISubject)
private
FObserversList : TList;
function GetObserver (index : integer) : IObserver;
public
constructor Create(AOwner: TComponent);
override;
destructor Destroy;
override;
procedure ObsAttach (Observer : IObserver);
procedure ObsDetach (Observer : IObserver);
procedure ObsNotify;
procedure ObsFreeNotify;
end;

procedure Register;
implementation
procedure TObserverComponent.ObsUpdate (Subject : ISubject);
begin
if assigned(FOnObsUpdate) then
FOnObsUpdate(self);
end;

procedure TObserverComponent.ObsFreeNotification (Subject : ISubject);
begin
FSubject := nil;
end;

procedure TObserverComponent.ObsSetSubject (Subject : ISubject);
begin
if Subject <> nil then
Subject.ObsAttach (self)
else
Subject.ObsDetach (self);
FSubject := Subject;
end;

{TSubjectComponent}
constructor TSubjectComponent.Create(AOwner: TComponent);
begin
inherited;
FObserversList := TList.Create;
end;

destructor TSubjectComponent.Destroy;
var
i : integer;
begin
ObsFreeNotify;
for i := 0 to FObserversList.Count - 1do
begin
GetObserver(i)._Release;
end;
FObserversList.Free;
inherited;
end;

procedure TSubjectComponent.ObsAttach (Observer : IObserver);
var
p : Pointer;
begin
p := Pointer(Observer);
if Assigned (Observer) then
begin
FObserversList.Add (p);
Observer._AddRef;
_AddRef;
Observer.ObsUpdate (self);
end;
end;

procedure TSubjectComponent.ObsDetach (Observer : IObserver);
var
i : integer;
begin
i := FObserversList.IndexOf (Pointer(Observer));
if i > 0 then
begin
GetObserver(i)._Release;
FObserversList.Delete(i);
end;
end;

procedure TSubjectComponent.ObsNotify;
var
i : integer;
begin
for i := 0 to FObserversList.Count - 1do
begin
GetObserver(i).ObsUpdate (self);
end;
end;

function TSubjectComponent.GetObserver (index : integer) : IObserver;
begin
Result := IObserver(FObserversList[index])
end;

procedure TSubjectComponent.ObsFreeNotify;
var
i : integer;
begin
for i := 0 to FObserversList.Count - 1do
begin
GetObserver(i).ObsFreeNotification (self);
end;
end;

{Register procedure : Adds components to palette}
procedure Register;
begin
RegisterComponents('Patterns', [TObserverComponent, TSubjectComponent]);
end;
The code from the three listings can be combined into a single unit and the
unit added to a package. Once you havedo
ne that, installing the package will
cause the TObserverComponent and TSubjectComponent objects to appear on the
Delphi toolbar. The TObserverComponent class has one event, OnObsUpdate, which
is where you plug in the code you want called when the subject changes state
. TSubjectComponent objects maintain a list of IObserver interfaces. To store
interfaces in a list, you need to convert them to pointers and then
cast the
pointer back to an interface when you want to use it. The interface list code
here is based on TInterfacedList written by Mike Scott, co-author of the
Merlin IDE enhancements for Delphi.
上面三个清单中的内容可合为一个单元加入包中。当你完成了上述操作,安装该包后
TObserverComponent 与 TSubjectComponent objects在Delphi Toolbar 中出现。
TObserverComponent class有一个事件,OnObsUpdate(其中你可插入在subject状态
改变时你想干什么的代码)。TSubjectComponent objects中保持了IObserver interfaces
的系列表。为在系列表中保存interfaces,你需要将它们先转为指针,然后在你要用到
Interface时将指针指向它。这里基于TInterfacedList的interface list 代码由
Mike Scott编写,他是Merlin IDE enhancements for Delphi的作者。
Now that we have implemented the observer pattern, we can return to the VAT
calculator program. Start a new project and drop three edit boxes on the
default form, arranged vertically. Set the text of Edit1 to '0'. Add a single
TSubjectComponent and a pair of TObserverComponents. Finally, add the code
below to the form, hooked into the Create method of the form, the change
method of the Edit box and the OnObsUpdate events of the two
TObserverComponents.
既然我们已经使用了observer 模式,现在可以回过头去看看VAT计算器的程序了。开始一
个新的project,在form中放置三个垂直排列的edit boxes。设置Edit1的Text为‘0’。
加上一个TSubjectComponent 和一组 TobserverComponents。最后,加入编码在相应的
地方。
Listing 3: TObserverComponent and TSubjectComponent: implementation of the Observer pattern
procedure TForm1.FormCreate(Sender: TObject);
begin
SubjectComponent1.ObsAttach(ObserverComponent1);
SubjectComponent1.ObsAttach(ObserverComponent2);
end;

procedure TForm1.Edit1Change(Sender: TObject);
begin
SubjectComponent1.ObsNotify;
end;

procedure TForm1.ObserverComponent1ObsUpdate(Observer: IObserver);
begin
if Edit1.Text <> '' then
Edit2.Text := FloatToStr(StrToFloat(Edit1.Text)*0.175);
end;

procedure TForm1.ObserverComponent2ObsUpdate(Observer: IObserver);
begin
if Edit1.Text <> '' then
Edit3.Text := FloatToStr(StrToFloat(Edit1.Text)*1.175);
end;
When you run the application, you should see the text in the two lower boxes
showing the VAT amount and the total amount as you type a number into the
topmost edit box. Since the conversion to numbers is rather fragile, you
need to limit what you type into Edit1. You can extend the demonstration to
include another form by adding an observer to the second form and calling
form1.SubjectComponent1.ObsAttach in the FormCreate event, matching it with
Form1.SubjectComponent1.ObsDetach in the FormDestroy event.
The TObserverComponent and TSubjectComponent classes can be used to join any
number of components together, the only thing you need to look out for
being circular references where changes to a component ripple through a
subject and observer back to the originating component.
当你开始执行application时,当你在最上面的Edit box 中输入一个数字时,你会看到
下面两个Edit box中分别显示VAT 值与总值。既然数字的转换相当灵活,你需要对Edit1
中输入加点限制。你可以对演示例子加以扩展,你可另加一个form,通过增加一个observer
到第二个form 并在FormCreate 事件中激活form1.SubjectComponent1.ObsAttach,
相应的在FormDestroy事件中有Form1.SubjectComponent1.ObsDetach。
TObserverComponent 与TSubjectComponent classes可以和其他的许多components结合
使用,唯一你需要注意的是being circular references where changes to a component
ripple through a subject and observer back to the originating component.
(上面这段怎么翻好请指教)。
You can use these two classes as presented or add their functionality to other
Delphi components or even to imported ActiveX controls, the neat thing being
that whatever youdo
as long as youdo
it by implementing
the IObserver and ISubject interfaces, it will work with anything else
that
understands those interfaces.
你可以依样使用这两个类或在别的Delphi components 中添加功能,甚至可以开发ActiveX
控件,通过IObserver 与ISubject interfaces的使用,不管你做什么,相应的在那些理解
这些interfaces的地方也会引起同样的动作。
 
>>delphikj
原文在那里?
 
http://www.delphibbs.com/delphibbs/dispq.asp?lid=700378
http://www.burn-rubber.demon.co.uk/patterns.htm
The TObserverComponent and TSubjectComponent classes can be used to join any
number of components together, the only thing you need to look out for
being circular references where changes to a component ripple through a
subject and observer back to the originating component.
这句后半段怎么翻好,请指教
 
>>being circular references where changes to a component
>> ripple through a subject and observer back to the originating component.
>>(上面这段怎么翻好请指教)。
>>这句后半段怎么翻好
TObserverComponent 与TSubjectComponent classes可以和其他的许多components结合
使用,唯一你需要注意的是子类构件和父构件的循环引用问题;
 
好像应这样:
TObserverComponent 与TSubjectComponent classes可以把许多components结合在一起,
唯一需要注意的是循环引用问题,此时对于一个component的改动会经由Subject和Observer
回到原来的component。
 
多人接受答案了。
 

Similar threads

S
回复
0
查看
3K
SUNSTONE的Delphi笔记
S
S
回复
0
查看
2K
SUNSTONE的Delphi笔记
S
D
回复
0
查看
1K
DelphiTeacher的专栏
D
I
回复
0
查看
611
import
I
I
回复
0
查看
499
import
I
顶部