天天看點

設計模式---Delphi 篇

本文以“觀察者”設計模式介紹 “對象--關系映射 ORM ”

下面以Delphi  tiOPF 架構實作原理介紹:

Abstract the Visitor logic

Before we go any further, we must abstract the Visitor functionality away from the business objects so we never have to touch it again. We will do this in three stages. We will create abstract TVisitor and TVisited classes, then we will create an abstract business object, and business object list that will descend from TVisited. We will then re-factor our TPerson and TPeople classes to descend from the newly created abstract classes.

The class diagram of what we are aiming to build looks like this:

設計模式---Delphi 篇

The TVisitor has two methods, AcceptVisitor( ) and Execute( ). Both taking a single parameter of type TVisited. The TVisited has a single method called Iterate( ) which takes a single parameter of type TVisitor. TVisited.Iterate( ) calls the Execute method on the Visitor that is passed as a parameter against itself, and if it contains other object, against each one of the too. The function TVisitor.AcceptVisitor is necessary because we are building a generic framework. It will be possible to pass a visitor that is designed for handling TPeople a concrete instance of, say a TDog and we must have a mechanism for preventing this from causing an access violation. The TVisited descends from TPersistent because down the track, we will be implementing some functionality that requires RTTI. The interfaces of TVisitor and TVisited are shown below:

TVisitor = class(TObject)
protected
  function AcceptVisitor(pVisited : TVisited): boolean; virtual; abstract;
public
  procedure Execute(pVisited : TVisited ); virtual; abstract;
end; // Both AcceptVisitor and Execute must be implemented in the concreate      
TVisited = class(TPersistent)
public
  procedure Iterate(pVisitor: TVisitor); virtual;
end;      

Both TVisitor.AcceptVisitor and TVisitor.Execute must be implemented in the concrete class. The implementation of TVisited.Iterate, which contains a call to TVisitor.Execute, is shown below:

procedure TVisited.Iterate(pVisitor: TVisitor);
begin
  pVisitor.Execute( self ) ;
end;      

Step #5. Create an abstract business object, and abstract list object

We require two more abstract classes in our framework: An abstract business object, and a list container for the abstract business object. We shall call these TtiObject and TtiObjectList and the interface of these classes is shown below:

TtiObject = class(TVisited)
private
public
  constructor Create; virtual;
end;      

We will be adding significantly to TtiObject when we look in more detail at the business object framework, but for the time being, we just add a virtual constructor so we can uniformly override the constructor in descendent classes.

We want our list class, TtiObjectList to descend from TVisited so the generic visitor behavior can be implemented (actually, we want it to descend from TtiObject for reasons we will discuss later). Ideally, we would use interfaces to give our list class the iteration behavior, but much of this code base predates the popularity of interfaces, and I have not faced up to the task of re-factoring to take advantage the benefits they can offer.

To create a list class which descends from TVisited and TtiObject, we shall use object containment. The interface of TtiObjectList is shown below and the implementation is pretty much what you would expect.

TtiObjectList = class(TtiObject)
private
  FList: TObjectList;
  function    GetList: TList;
public
  constructor Create; override;
  destructor  Destroy; override;
  property    List: TList read GetList;
  procedure   Iterate( pVisitor: TVisitor); override;
  procedure   Add(pData: TObject);
end ;      

The most important method in this class is the overridden procedure Iterate. In the abstract class TVisitor, iterate is implemented as the one line call pVisitor.Execute(self). In the TtiObjectList it is implemented like this:

procedure TtiObjectList.Iterate(pVisitor: TVisitor);
var
  i : integer ;
begin
  inherited Iterate(pVisitor);
  for i := 0 to FList.Count - 1 do
    (FList.Items[i] as TVisited).Iterate(pVisitor);
end;      

This is an important core concept. We now have two abstract business objects TtiObject and TtiObjectList. Both have an Iterate method that is passed an instance of a TVisitor as a parameter. In each case, the TVisitor’s Execute method is called with self as a parameter. This call is made via inherited at the top of the hierarchy. For the TtiObjectList class, each object in the owned list also has its Iterate method called with the visitor being passed as the parameter. This ensures that all objects in the hierarchy are touched by the Visitor.

Step #6. Create a Visitor manager

Now, back to the original problem we created for our selves in step #3. We don’t want to be spending all our time creating and destroying visitors. The solution is in the Visitor Manager.

The Visitor Manager performs two main tasks: It maintains a list of registered visitors (visitors are registered in the implementation section of the unit where they are declared.); and calls a group of visitors that are registered with a given command name against the data object it is passed.

To implement the Visitor manager, we will define three more classes: The TVisClassRef, TVisMapping and the TtiVisitorManager.

The TVisClassRef is a class reference type that will hold an instance TVisitor’s class. I find the help text description of class references a little confusing. The best way to understand them is with an example. Lets say we have our abstract Visitor class TVisitor, and a Visitor class reference type TVisClassRef. We also have a concrete Visitor called TSaveVisitor. The TVisClassRef type is declared like this:

TVisClassRef = class of TVisitor ;      

This lets us write code like this:

procedure ExecuteVisitor(const pData: TVisited; const pVisClass: TVisClassRef);
var
  lVisitor: TVisitor;
begin
  lVisitor := pVisClass.Create;
  try
    pData.Iterate(lVisitor);
  finally
    lVisitor.Free;
  end;
end;      

We pass two parameters to this procedure; pData which is an instance of TVisited (like our TPeople), a TVisClassRef, which could be TShowNameVisitor or TShowEMailAdrsVisitor. This procedure takes care of the tedious business of creating the visitor, calling iterate, then freeing the visitor when done.

The second class we create for our visitor manager is called TVisMapping. It is a simple data structure to hold two pieces of information: a TVisClassRef and a string called Command. The interface of TVisMapping is shown below:

TVisMapping = class(TObject)
private
  FCommand: string;
  FVisitorClass: TVisClassRef;
public
  property VisitorClass: TVisClassRef read FVisitorClass write FVisitorClass;
  property Command: string read FCommand write FCommand;
end;      

The final class we create is the TtiVisitorManager. When we register a Visitor with the Visitor Manager, an instance of TVisMapping is created and added to the list inside the TtiVisitorManager. The command and VisitorClass properties are set which allows us to execute a group of visitors identified by a string. The interface of the TtiVisitorManager is shown below:

TtiVisitorManager = class(TObject)
private
  FList: TObjectList;
public
  constructor Create;
  destructor  Destroy; override;
  procedure   RegisterVisitor(const pCommand: string; pVisitorClass: TVisClassRef);
  procedure   Execute(const pCommand: string; pData: TVisited);
end;      

The key methods here are RegisterVisitor and Execute. RegisterVisitor is called in the implementation section of the unit where the Visitor is defined and is typically called like this:

initialization
   gTIOPFManager.VisitorManager.RegisterVisitor('show', TShowNameVisitor);
   gTIOPFManager.VisitorManager.RegisterVisitor('show', TShowEMailAdrsVisitor);      

The implementation of RegisterVisitor is shown below (this code is much the same as the code found in a Delphi implementation of the Factory Pattern)

procedure TtiVisitorManager.RegisterVisitor(const pCommand: string; 
    const pVisitorClass: TVisClassRef);
var
  lData: TVisMapping;
begin
  lData := TVisMapping.Create;
  lData.Command := pCommand;
  lData.VisitorClass := pVisitorClass;
  FList.Add(lData);
end;      

The other important method in the TtiVisitorManager is Execute. Execute takes two parameters, the command name which identifies the family of visitors to be executed, and the data object which is at the top of the tree to be iterated over. The implementation of Execute is shown below:

procedure TtiVisitorManager.Execute(const pCommand: string; 
    const pData: TVisited);
var
  i: integer;
  lVisitor: TVisitor;
begin
  for i := 0 to FList.Count - 1 do
    if SameText(pCommand, TVisMapping(FList.Items[i]).Command) then
    begin
      lVisitor := TVisMapping(FList.Items[i]).VisitorClass.Create;
      try
        pData.Iterate(lVisitor);
      finally
        lVisitor.Free;
      end;
    end;
end;      

To execute both the ShowName and ShowEMailAdrs visitors (the ones we registered above), one after the other we would make the following call to the Visitor manager.

gTIOPFManager.VisitorManager.Execute('show', FPeople);      

Next, we will create some persistent Visitors that will let us make calls like

// To read from a text file
gTIOPFManager.VisitorManager.Execute('read', FPeople);
// To save to a text file
gTIOPFManager.VisitorManager.Execute('save', FPeople);      

but first we will use the tiListView and tiPerAwareControls to create a GUI to use while editing the list of TPeople.

參考更多:

1、http://tiopf.sourceforge.net/Doc/Concepts/2_TheVisitorFramework.shtml

2、微軟.NET架構 關于“觀察者”模式、委派/事件:

http://www.microsoft.com/china/MSDN/library/enterprisedevelopment/builddistapp/ExploringtheObServerDesignPattern.mspx?mfr=true

《未完待續07.04.07》 即将補充完善,敬請關注!!!

繼續閱讀