(Ebook) Delphi - Modelmaker Design Patterns - Mmdesignpatterns
(Ebook) Delphi - Modelmaker Design Patterns - Mmdesignpatterns
(Ebook) Delphi - Modelmaker Design Patterns - Mmdesignpatterns
ModelMaker 5
Including a Design Patterns Example
ModelMaker
c/o Gerrit Beuze and Rene Post
Stenenkruis 27 B
6862 XG Oosterbeek
Netherlands
http:\\www.modelmaker.demon.nl
info@ modelmaker.demon.nl
ModelMaker 5
ModelMaker
c/o Gerrit Beuze and Rene Post
Stenenkruis 27 B
6862 XG Oosterbeek
Netherlands
http:\\www.modelmaker.demon.nl
info@ modelmaker.demon.nl
All brand and product names are trademarks or registered trademarks of their respective
holders.
The latest version of this manual will be availabe on the ModelMaker website.
: G. Beuze
DesignPatterns5, 2
ModelMaker 5
Contents
Introduction 5
Design Patterns 6
Introduction 6
What is a design pattern 6
Patterns in ModelMaker 7
Applying patterns 7
Supported patterns 8
Wrapper pattern 9
Origin 9
Intent 9
Motivation 9
Implementation 10
How to apply the pattern 11
How the pattern acts on changes 12
Mediator pattern 13
Origin 13
Intent 13
Motivation 13
Implementation 14
How to apply the pattern 16
How the pattern acts on changes 17
Singleton pattern 18
Origin 18
Intent 18
Motivation 18
Implementation 18
How to apply the pattern 21
How the pattern acts on changes 21
Decorator pattern 21
Origin 21
Intent 22
Motivation 22
Implementation 23
How to apply the pattern 26
How the pattern acts on changes 28
Lock pattern 28
Origin 28
Intent 28
Motivation 28
Implementation 29
How to apply the pattern 30
How the pattern acts on changes 30
Visitor pattern 31
Origin 31
DesignPatterns5, 3
ModelMaker 5
Intent 31
Motivation 31
Implementation 33
How to apply the pattern 35
How the pattern acts on changes 36
Observer pattern 37
Origin 37
Intent 37
Motivation 37
Implementation 37
Transforming events using the Observer Wizard 40
How to apply the pattern 40
How the pattern acts on changes 40
Reference Count pattern 40
Origin 40
Intent 41
Implementation 41
Patterns example 42
Introduction 42
Bibliography 55
DesignPatterns5, 4
Introduction
ModelMaker represents a brand new way to develop classes and component packages for
Borland Delphi. ModelMaker is a class tree oriented CASE tool specifically designed for
generating native Delphi code (in fact it was made using Delphi and ModelMaker). Delphi's
Object Pascal language is fully supported by ModelMaker. From the start ModelMaker was
designed to be a smart and highly productive tool. It has been used to create classes for both
real-time / technical and database type applications.
In ModelMaker you'll find a synergy of ideas from the cutting edge of object-oriented and
component-based technology. It was inspired by the work of methodologists like Ivar
Jacobson (OOSE), Ralph Johnson (design patterns) and Karl Lieberherr (adaptive software).
A unique feature, currently not found in any development environment for Delphi, is the
support for design patterns. A number of patterns from the book ‘Design Patterns, Elements of
Reusable Object Oriented Software’ by [Gam+] are implemented as ‘ready to use’ active
agents. A ModelMaker Pattern will not only insert Delphi style code fragments to implement a
specific pattern, but it also stays ‘alive’ to update this code to reflect any changes made to the
design.
This manual contains the Design Patterns reference and a design patterns demo. Before you go
through this manual and demo you should be fairly accustomed with ModelMaker. Refer to
the Users manual for “Getting Started”and “Basic concepts”.
ModelMaker 5
Design Patterns
Introduction
When developing ModelMaker we were inspired a lot by the book ‘Design Patterns, Elements
of Reusable Object-Oriented Software’ [Gam+] We decided to make design patterns an
integral part of ModelMaker’s modelling engine. Thus enabling us to support design patterns
in a brand new way: as active agents looking over your shoulder while you are editing your
model and automatically reflecting changes where ever needed, keeping the pattern’s
implementation up to date.
Now that’s nice, but what is a design pattern? The next paragraph will summarise what does
[Gam+] say about this. And how patterns are implemented in ModelMaker.
This way design patterns make it easier to reuse successful designs and architectures.
Expressing proven techniques as design patterns makes them more accessible to developers of
new systems. Design patterns help you choose design alternatives that make a system reusable
and avoid alternatives that compromise reusability. Design patterns can even improve the
documentation and maintenance of existing systems by furnishing an explicit specification of
class and objects interactions and their underlying ‘intend’. Put simply, design patterns help a
designer get a design ‘right’ faster.
The design patterns as described by [Gam+] and as used in ModelMaker are not about designs
such as linked lists and hash tables that can be encoded in classes and reused as is. Nor are
they complex, domain specific designs for an entire application or subsystem.
DesignPatterns5, 6
ModelMaker 5
Patterns in ModelMaker
In ModelMaker patterns are part of the modelling engine, just like classes and units. Before
you apply a specific pattern, you have to provide it’s context, such as selecting the class(es)
and / or member(s) on which to operate. When a pattern is applied, it will insert (user
adjustable) code into the model. This usually consists of classes, members or code sections in
methods. The total of all inserted parts makes up the complete implementation of the pattern.
So far the pattern is like a smart macro which is able to reason about what to insert where.
The power of ModelMaker is that patterns stay alive after inserting the pattern related code.
Because the pattern is alive, it can reflect changes in the model to the pattern related code or
even automatically add or delete members if needed.
As a bonus you get an implementation in Delphi’s Object Pascal of the most common
patterns. We’ve been using the supported patterns a lot in many projects and of course in
developing ModelMaker itself.
It is important to notice that you won’t see a pattern it self somewhere in the unit’s source
code. The pattern is represented there only by means of the classes, members and code
sections it inserted.
A pattern is the ‘Owner’ of anything it inserts. This implicates for example that only the
pattern can update pattern related sections of code in a method. Furthermore, whenever you
remove the pattern from the model, it will withdraw and delete all classes, members and
sections of code it ‘Owns’. Even if you added your own sections of code to a method the
patterns owns. Therefore be careful inserting sections of code in pattern owned methods.
Sometimes it is wiser to add a new method which contains your code which is then called
from within the pattern’s method or vice versa.
You can apply a pattern multiple times in a model or even on the same class. You should
however make sure that each pattern has it’s own unique context. For example: it does not
make sense to apply a singleton pattern twice on the same class (which is the only context a
singleton pattern needs), and the second singleton pattern applied, will not create correct code
since it will conflict with the first singleton’s code. Patterns might even interact with or extend
each other as will be show in the patterns demo in the next chapter.
Applying patterns
All patterns active in the model are displayed in the Patterns view - refer to the GUI reference
for details on this view. In this view you apply new patterns or delete existing ones. On top of
the Patterns view you’ll find the Pattern palette. This palette contains all patterns ordered by
type, just like Delphi’s Component Palette. Patterns are applied by clicking the appropriate
button in the pattern palette.
Usually you need to provide a context for the pattern before it actually can be applied. This
consists at least of selecting a class or class member on which the pattern will operate. Please
refer to the sections describing the specific patterns for more precise details about a pattern’s
initial context.
DesignPatterns5, 7
ModelMaker 5
If patterns become ‘invalid’ because you removed some essential part of the pattern, the
pattern will delete all it’s owned classes, members and sections of code. The pattern is not
active anymore and must be manually deleted from the patterns view. This pattern is displayed
in the patterns view as ‘INVALID’.
You can also tell a pattern to withdraw it’s ownership from all parts it inserted (classes,
members and sections of code) without removing those parts. Use the ‘Release Ownership’ on
a pattern. The pattern itself will become invalid, but all parts become user owned which
means you can manually edit or delete them.
The next sections will describe the specific patterns and give some code examples. In these
examples we’’ use the following styles:
Supported patterns
When deciding which of the patterns as described in [Gam+] to support, we found that many
patterns could be expressed using the same class and instance relations. For example the
structure of a Proxy pattern is the same as a that for a Decorator pattern. Also many patterns
can be implemented very easy using ModelMaker’s ability to override methods and keep
overridden methods restricted to their origins. Some patterns were hard to implement the
ModelMaker way and little would be gained in doing so. Other patterns are so class library
restricted that it didn’t feel right to implement them in a generic tool. This reduced the number
of patterns to the following list.
DesignPatterns5, 8
ModelMaker 5
As we keep in touch with the pattern community, new patterns will be supported in time.
We’re also open to suggestions from users, so if you come up with new ideas about (generic)
patterns to implement, please inform us.
Wrapper pattern
Origin
ModelMaker’s Wrapper pattern is based on the ‘Adapter’ or ‘Wrapper’ pattern described in
[Gam+, pages 139..150]. For a more detailed description refer to this book.
Intent
‘A wrapper converts the interface of a class into another interface clients expect. Wrappers let
classes work together that couldn’t otherwise because of incompatible interfaces’
[Gam+ 139].
Motivation
In Delphi’s Object Pascal language polymorphism is based on class type rather than on the
supported interfaces. This implicates that although two classes can support the same interface,
they must have the same ancestor in order to be polymorphically exchangeable for client
object. Sometimes you want existing but unrelated classes to work together. The wrapper
pattern lets you wrap (parts of) the interface of a class by an other class. This simulates
multiple inheritance in Delphi by use of ‘uses’ relations.
You could for example have an existing class TSample inheriting from TObject which you
want to add to Delphi’s component palette. However, Components must descend from
TComponent. Assuming that there is a good reason for not changing TSample’s ancestor class
to TComponent (for example you don’t have the source), you could create a new class
TSampleWrapper which inherits from TComponent and which ‘uses’ or ‘consists of’ a
TSample. Since the TSampleWrapper is a TComponent descendant it can be added to the
component palette. You could now ‘wrap’ the interface of TSample and make it available in
the TSampleWrapper class. The TSampleWrapper delegates the actual behaviour to it’s
wrapped Sample. It does this by calling the appropriate method in Sample or access the
appropriate property in Sample.
Another reason to use a wrapper pattern is that it helps to stick to the ‘Law of Demeter’. This
law basically tells you not to reference objects more than one level deep. Assume the
interfaces of the classes TSample and TSampleWrapper to be like:
type
TSample = class (TObject)
private
FSomeValue: Integer;
public
function SomeAction(const Data: string): Boolean;
DesignPatterns5, 9
ModelMaker 5
Actually, code like ListBox.Canvas.Brush.Color violates this law, since we reference three
levels deep.
Implementation
We’ll use the above described classes to demonstrate the implementation of a wrapper using
ModelMaker’s wrapper pattern. In the example TSampleWrapper ‘consists of’ a TSample
which is referenced in the Sample property. The ModelMaker wrapper pattern can now make
the method SomeAction and property SomeValue available to the TSampleWrapper interface
and fully implement the wrapped members.
This allows users of TSampleWrapper for example to access the SomeAction method
immediately without referencing the SampleWrapper.Sample.SomeAction. The
implementation for this interface will be: (note that this is fully implemented, and ready to
compile)
DesignPatterns5, 10
ModelMaker 5
• All members in the wrapper class have the same attributes (Name, Data type, Visibility
etc.) as the members they wrap.
• Properties are wrapped using properties using read and / or write access methods to access
the wrapped property.
• Fields are also wrapped using properties using read and write access methods to access the
wrapped field.
• Events are wrapped by events, using read and write access methods to access the wrapped
event rather than event handlers.
• Methods are wrapped by methods which simply pass on the call and parameters to the
wrapped class, returning whatever the wrapped method returned.
DesignPatterns5, 11
ModelMaker 5
Scope filter
Inheritance filter
4. The wrapper pattern editor will appear. In this dialog, select the members to wrap using the
filters (usually methods, properties, sometimes events) and click OK.
The pattern will now be listed in the Patterns view and members and code will be inserted for
each wrapped member.
If wrapped members are deleted from the wrapped class, they will also be deleted from the
wrapping class. Be aware that this will also remove any methods and their implementation
needed to access the wrapped members, even if you added some user owned fragments to
these methods. So if you delete TSample.SomeAction in the example, the pattern will delete
the TSampleWrapper.SomeAction method.
DesignPatterns5, 12
ModelMaker 5
If new members are added to the wrapped class that need to be wrapped, or you want to
‘unwrap’ some members, select the pattern in the Patterns view and click the ‘Edit pattern’
button. The wrapper pattern editor will appear and you may select the new set of members to
be wrapped.
If you delete the wrapper pattern or the reference to the wrapped part, the pattern will delete
all wrapped members in the wrapper class. If you delete the wrapper class, the wrapped class
or the reference to the wrapped class, the pattern will become ‘invalid’ and all wrapped
members in the wrapper class will be deleted.
Mediator pattern
The mediator pattern as currently implemented will become obsolete in future versions of
ModelMaker. It will be replaced by new design tools. Although future versions will still
support the pattern and the pattern is found to fairly stable and reliable, it is not maintained
anymore.
Origin
ModelMaker’s Mediator pattern is based on the Mediator pattern as described in
[Gam+, pages 273..282]. The Delphi implementation is ours.
Intent
‘Define an object that encapsulates how a set of objects interact. Mediator promotes loose
coupling by keeping objects from referring to each other explicitly, and it lets you vary their
interaction independently’. [Gam+, page 273]
Motivation
‘Object oriented design encourages the distribution of behaviour among objects. Such
distribution can result in an object structure with many connections between objects; in the
worst case, every object ends up knowing about every other.
Though partitioning a system into many objects generally enhances reusability, proliferating
interconnections tend to reduce it again. Lots of interconnections make it less likely that an
object can work without the support of others - the system acts as though it were monolithic.
Moreover it can be difficult to change the systems’ behaviour in any significant way, since
behaviour is distributed among many objects. As a result, you may be forced to define many
subclasses to customise the system’s behaviour.’ [Gam+ page 273].
Although there’s a lot more to tell about mediators, we won’t get into details here, please refer
to [Gam+]. That’s not because you don’t need mediators, but because you already are familiar
with them: Delphi provides great mediators.
DesignPatterns5, 13
ModelMaker 5
Developers at Borland probably were aware of the above mentioned paragraphs when
designing the Delphi Object Pascal language and the VCL components. Their solution to this
problem is essentially: events (or method pointers). The use of events makes de-coupling of
objects possible. The class TForm is their standard mediator class which handles (wires)
events from components put on the form. They even delivered a great tool with it: the Object
Inspector. Being able to delegate behaviour to another class using events, saves you, for
example, from subclassing TButton when you need a button that interacts with an TEdit
control. The mediator is the form which handles all events and takes corresponding actions,
coupling all related components together with snippets of code. Great. So what’s the need for
a Mediator pattern?
Delphi’s Object Inspector uses RTTI and form designers to create event handlers on forms
and Delphi’s component streaming mechanism takes care of actually wiring these events at
run time.
If you ever manually created event handlers you know that you have to:
• Create a event handler method with the correct parameter list and possibly the correct
function result.
• Wire the event handler to the event with code like: FSample.OnChange :=
SampleChange;
• Make sure you unwire the event again in appropriate cases to avoid wired objects from
calling destroyed or otherwise illegal objects.
This now is what ModelMaker’s mediator pattern is able to do for you. In order to construct a
correct event handler method it needs the event type definition. Remember however that while
designing your code in ModelMaker there is no RTTI information available. Therefore
ModelMaker uses an event library which defines the event type definitions. The mediator
pattern relies on these event type definitions to construct and update event handlers.
Implementation
Suppose we’ve created a class TComPort which encapsulates a serial port device driver. This
class defines a series of events to asynchronously inform clients of state changes: data
triggers, line errors etc. A TController class could ‘use a’ TComPort class to transmit data
through a serial port.
type
» TLineEvent = procedure (Sender: TObject; Error: TLineError) of object;
DesignPatterns5, 14
ModelMaker 5
published
» property OnDataReceived: TTriggerEvent read FOnDataReceived write
FOnDataReceived;
» property OnLineEvent: TLineEvent read FOnLineEvent write FOnLineEvent;
» property OnTimeOut: TNotifyEvent read FOnTimeOut write FOnTimeOut;
» property OnTransmitted: TNotifyEvent read FOnTransmitted write
FOnTransmitted;
end;
implementation
constructor TController.Create;
begin
inherited Create;
» FComPort := TComPort.Create;
end;
destructor TController.Destroy;
begin
» FComPort.Free;
inherited Destroy;
end;
Now have a look at the same abstract for class TController with the mediator applied to this
structure:
implementation
constructor TController.Create;
begin
inherited Create;
» FComPort := TComPort.Create;
{ now wire comport calling the method created by the mediator }
» WireComport;
end;
destructor TController.Destroy;
begin
{ make sure the comport is unwired again }
» UnwireComport;
» FComPort.Free;
inherited Destroy;
end;
DesignPatterns5, 15
ModelMaker 5
procedure TController.UnwireComPort;
begin
FComPort.OnDataReceived := nil;
FComPort.OnLineEvent := nil;
FComPort.OnTimeOut := nil;
FComPort.OnTransmitted := nil;
end;
procedure TController.WireComPort;
begin
FComPort.OnDataReceived := ComPortDataReceived;
FComPort.OnLineEvent := ComPortLineEvent;
FComPort.OnTimeOut := ComPortTimeOut;
FComPort.OnTransmitted := ComPortTransmitted;
end;
DesignPatterns5, 16
ModelMaker 5
Visibility filter
Inheritance filter
4. The Mediator editor will appear. In this dialog, select the events to mediate. Use the
inheritance filter to display inherited events. If you want the pattern to create a Wire and
Unwire method, check the corresponding options. Click OK to apply the pattern.
The pattern will now be listed in the Patterns view and members and event handlers will be
inserted for each mediated event.
Editing the pattern lets you, (re)set the creation of Wire and Unwire methods. Also new events
may be mediated.
DesignPatterns5, 17
ModelMaker 5
If you delete the pattern, it will remove all event handlers and the wire and unwire methods.
The same will happen if you invalidate the pattern by deleting the reference or the mediated
class.
Singleton pattern
Origin
ModelMaker’s singleton pattern is based on the singleton pattern as described in
[Gam+, pages 127..134]. The Delphi implementation for the pattern is ours.
Intent
Ensure a class only has one instance, and provide a global point of access to it [Gam+,p127].
Motivation
The motivation is based on [Gam+, page 127]. It’s important for some classes to have exactly
one instance. Although there can be many printers in a system, there should be only one
printer spooler. There should be only one file system (or file system manager) and one
window manager.
How do we ensure that a class has only one instance and that the instance is easily accessible?
A global variable makes an object accessible, but it doesn’t keep you from instantiating
multiple objects. Global variables also tend to pollute the name space.
A better solution is to make the class itself responsible for keeping track of it’s sole instance.
The class can ensure that no other instance can be created (by intercepting requests to create
new objects), and it can provide a way to access the instance. This is the singleton pattern.
Typical use of this pattern is in service like classes.
The ModelMaker singleton also adds automatic instantiation to this pattern. As soon as you
reference the instance, it is instantiated automatically.
Implementation
Consider for example a class TProgressor which could be used as a low level service to deal
with time consuming processes. Typical methods would be: StartProgress, EndProgress,
Abort and typical properties would be Progress, Aborted etc. The following example shows
part of TProgressor’s interface:
type
TProgressor = class (TObject)
private
FProgress: Integer;
protected
DesignPatterns5, 18
ModelMaker 5
Refer to The TProgressor class, page 42, where this class is explained in more detail.
Applying the singleton pattern to this class results in the following interface:
type
TProgressor = class (TObject)
private
FProgress: Integer;
protected
constructor CreateInstance;
class function AccessInstance(Request: Integer): TProgressor;
procedure SetProgress(Value: Integer);
public
constructor Create;
destructor Destroy; override;
class function Instance: TProgressor;
class procedure ReleaseInstance;
» procedure StartProgress;
» property Progress: Integer read FProgress write SetProgress;
end;
constructor TProgressor.Create;
begin
inherited Create;
raise Exception.CreateFmt('Access class %s through Instance only',
[ClassName]);
end;
constructor TProgressor.CreateInstance;
begin
inherited Create;
end;
destructor TProgressor.Destroy;
begin
if AccessInstance(0) = Self then AccessInstance(2);
inherited Destroy;
end;
DesignPatterns5, 19
ModelMaker 5
procedure TProgressor.StartProgress;
begin
» { Placehere the implementation of the progress mechanism ]
end;
The secret of this pattern is in the AccessInstance method. This method uses a typed
constant to store the instance. We needed to use this work around because Delphi doesn’t
support (static) class fields in classes. Depending on the parameter Request the method will
either return the instance (Request = 0), create an instance (Request = 1), reset the instance
to nil (Request =2). Delphi 2/3/4 allow this use of typed constants only if the $J+ compiler
switch is set.
By making the constructor CreateInstance protected, we assure that no other class is able to
call this constructor. It may however be made virtual and can be overridden in descendant
classes. In that case the first class to call Instance will determine the actual type to be
instantiated.
The only code you manually need to add, is the clean up code in the unit’s initialization
or finalization section. This could be something like: (example shows Delphi 1.0 code, for
Delphi 2/3/4 code refer to Making TProgressor a singleton class, page 47):
unit <!Unitname!>;
...
...
implementation
...
...
DesignPatterns5, 20
ModelMaker 5
end;
initialization
MMWIN:START INITIALIZATION
» AddExitProc(ShutDown);
end.
procedure TSomeClass.DoSomething;
var I: Integer;
begin
» TProgressor.Instance.StartProgress;
» for I := 0 to 100 do
» begin
» TProgressor.Instance.Progress := I;
» ..{ do something useful }
» end;
» TProgressor.Instance.EndProgress;
end;
The pattern cannot be ‘edited’ in the pattern view, since there’s nothing to edit. If you remove
the pattern, all associated methods are also removed.
Decorator pattern
Origin
ModelMaker’s Decorator pattern is based on the Decorator pattern as described in
[Gam+, pages 175..184].
DesignPatterns5, 21
ModelMaker 5
Intent
‘Attach additional responsibilities to an object dynamically. Decorators provide a flexible
alternative to subclassing for extending functionality’ [Gam+ page 175].
Motivation
The motivation is based on fragments of the motivation described by [Gam+, page 175,176].
Sometimes we want to add responsibilities to individual objects, not to an entire class.
Suppose we have a family of classes used to output lines of text. The abstract base class
TTextStream defines an interface, descendants like TTextFile, TLinePrinter and
TClipboardStream implement this interface.
TTextStream
Now suppose we want to add behaviour to this family like buffering text, scrambling text and
performing textual analysis while writing the text.
One way to add responsibilities is with inheritance. Inheriting a buffer from TTextStream will
buffer output for every subclass instance. This is inflexible, however, because the choice of
buffering is made statically. A client can’t control how and when to let the stream be buffered.
Also, this loads the abstract class TTextStream with fields to control buffering which are
carried by each instance. In general it is best to keep (abstract) base classes high up in the
hierarchy as light weight as possible. Adding scrambling and textual analysis to the base class
will make this class even heavier.
If we don’t want to create heavy weight base classes another problem arises. In this case a
large number of independent extensions are possible and would produce an explosion of
subclasses to support every combination: TBufTextFile, TScrambledTextFile,
TBufScrambledTextFile, TBufLinePrinter, TScrambledLinePrinter etc. The same
problem arises if a class definition is hidden or otherwise unavailable for subclassing. For
example, if you want to add new behaviour to a class high up in a third party class library: try
to add new behaviour to Delphi’s TStream class!
A more flexible approach is to enclose a text stream in another object that just adds buffering
or scrambling. The enclosing object is called a decorator. The decorator conforms to the
interface of the text stream it decorates so that it’s presence is transparent to the text stream’s
clients. Conforming to an interface in Delphi implicates inheriting from a common ancestor,
in this case TTextStream. The decorator forwards requests to the text stream it decorates and
may perform additional actions (such as buffering or scrambling the text) before or after
DesignPatterns5, 22
ModelMaker 5
forwarding. Transparency lets you nest decorators recursively, thereby allowing an unlimited
number of added independent responsibilities.
type
TTextStream = class (TObject)
protected
function GetEndOfText: Boolean; virtual; abstract;
public
function ReadLine: string; virtual; abstract;
procedure WriteLine(const Line: string); virtual; abstract;
property EndOfText: Boolean read GetEndOfText;
end;
Output TextStream
TClient TUpperCaseFilter TTextFile
Implementation
We’ll use the above described classes to demonstrate the implementation of a decorator using
ModelMaker’s decorator pattern. In this example, TTextStream defines an (abstract) interface
DesignPatterns5, 23
ModelMaker 5
which is decorated by a TTextFilter class. ModelMaker’s decorator pattern will create the
TTextFilter class and let you select which methods to decorate. Here’s the implementation:
type
TTextStream = class (TObject)
protected
function GetEndOfText: Boolean; virtual; abstract;
public
function ReadLine: string; virtual; abstract;
procedure WriteLine(const Line: string); virtual; abstract;
property EndOfText: Boolean read GetEndOfText;
end;
destructor TTextFilter.Destroy;
begin
TextStream := nil;
inherited Destroy;
end;
DesignPatterns5, 24
ModelMaker 5
end;
It’s really easy to create a text filter converting text to uppercase now, using ModelMaker’s
method override wizard:
type
TUpperCaseFilter = class (TTextFilter)
public
» function ReadLine: string; override;
» procedure WriteLine(const Line: string); override;
end;
implementation
This filter could now be used to decorate any text stream target:
DesignPatterns5, 25
ModelMaker 5
procedure TClient.ListContents;
var
T: TTextStream;
begin
T := CreateOutput;
{ At this point, we don't know if we're talking to a decorated output or not }
try
{ list contents to T }
T.WriteLine('Contents');
finally
T.Free;
end;
end;
It’s not spectacular, but it demonstrates the implementation and use of a decorator. You could
imagine far more complex functionality to add using decorators, such as buffering, scrambling
textual analysis etc.
DesignPatterns5, 26
ModelMaker 5
The decorator pattern editor will appear whenever you apply or edit a decorator pattern. Use
this editor to change the ‘Decorator Class Name’ to your needs. Note that you cannot use the
class editor from the classes view to change the decorator’s class name. When the pattern is
applied, this class is created. Next time you edit the pattern this class is just renamed.
You may select a class to decorate. Initially this is the class you selected when you applied the
pattern. Once the pattern has been applied, this class cannot be changed anymore.
In the ‘Reference name’ edit, you may edit the reference property’s name to your needs, in our
example we used TextStream. Note that this is the only place to change the reference
property’s name, you cannot change it using the property editor from the Class Members
view. The same applies to the ‘Ownership name’ property (OwnStream in the example).
The options ‘Reference in Create’ and ‘Ownership in Create’ let you define the parameter list
of the constructor Create. The properties themselves are created irrespective of these options.
The most important section is the list of decorated methods. You add methods to decorate by
clicking the ‘Add decorated methods’ button. A dialog will let you select the methods to be
decorated. Although a filter lets you select any type of method, be aware that the
implementation of the pattern relies on non-static methods. Static methods won’t be available
to clients using the decorator without using RTTI.
DesignPatterns5, 27
ModelMaker 5
If you edit the pattern from the patterns view, you may add new methods to decorate or
remove decorated methods, edit the decorator class’s name and the property names. The
pattern will reflect the changes to the model.
The pattern will be invalidated if you delete the decorated class, since there’s nothing to
decorate anymore.
If you delete the pattern, or it is invalidated, the pattern will delete the decorator class,
including all it’s members. Any classes descending from the decorator class will be
untouched, although their ancestors will have changed.
Lock pattern
Origin
ModelMaker’s lock pattern is based on Delphi’s update locking mechanism in the TStrings
class.
Intent
Provide a mechanism to temporarily lock some aspects of a class.
Motivation
Often you’ll find that a object dispatches notifications as a result of changing it’s internal
state. Clients will handle these notifications to synchronise with the object. If multiple
changes are to be applied at once, this will result in multiple notifications and subsequent
synchronisations. The lock pattern let’s you temporarily lock an aspect of the class, avoiding
unneeded notifications. Locking can be applied nested.
Consider for example a TBag class which implements collection like behaviour. TBag
dispatches an OnChange event each time something changes. If we wanted to add multiple
objects to a bag, this would result in multiple change notifications. A GUI control wiring the
OnChange event would have to be repainted each time, resulting in a poor performance. The
lock pattern enables locking the bag before adding the objects. When the objects are added,
the bag is unlocked again, which will result in a single dispatch of OnChange. The advantage
DesignPatterns5, 28
ModelMaker 5
of using a lock pattern rather than setting a Boolean flag, for example FUpdating, is that calls
to Lock and Unlock can be nested.
Implementation
The implementation for the lock pattern applied to a class TBag is: (only pattern related code
is shown)
type
TBag = class (TObject)
private
FLockCnt: Integer;
protected
function Locked: Boolean;
procedure SetLocking(Updating: Boolean);
public
procedure Lock;
procedure UnLock;
end;
implementation
procedure TBag.Lock;
begin
Inc(FLockCnt);
if FLockCnt = 1 then SetLocking(False);
end;
procedure TBag.UnLock;
begin
Dec(FLockCnt);
if FLockCnt = 0 then SetLocking(True);
end;
Notice how calls to Lock and Unlock always must be paired to avoid the Bag to remain locked
forever. Therefore it is wise to use a try..finally block to make sure pairs are always matched,
as is demonstrated in the following example.
DesignPatterns5, 29
ModelMaker 5
procedure TBag.Change;
begin
if not Locked then
if Assigned(FOnChange) then FOnChange(Self);
end;
Since this mechanism can be used in many situations, the lock patterns let’s you edit the
patterns field and method names. This pattern is one of the few patterns that can be applied to
the same class more than once and still be meaningful. You would of course need different
names, such as LockUpdate, LockScreenUpdate or BeginUpdate.
DesignPatterns5, 30
ModelMaker 5
If you delete the pattern, it will delete the inserted members. If you delete the class the pattern
is applied to, the pattern is invalidated.
Visitor pattern
Origin
ModelMaker’s Visitor pattern is based on the Visitor pattern as described in
[Gam+, pages 331..344]. The implementation in Delphi’s Object Pascal language is ours.
Intent
‘Represents an operation to be performed on the elements of an object structure. Visitor lets
you define a new operation without changing the classes of the elements on which it operates’
[Gam+, page 331].
Motivation
The motivation is an adjusted version of the motivation as described in
[Gam+, page 331..332].
Most of these actions will need to treat members that represent fields differently from
members that represent methods or properties. Hence there will be one class for fields, another
for methods, and so on. The set of member types, which is dependent on the target language,
TMember
DesignPatterns5, 31
ModelMaker 5
This diagram shows part of the member hierarchy. The problem here is that distributing all
these operations across the various member classes leads to a system that’s hard to
understand, maintain and change. It will be confusing to have drawing behaviour code mixed
with code generation or help file generation code. Moreover adding a new operation usually
will have to be implemented in all off the member classes, which will spread the related code
around and requires recompiling all of these classes. It would be better if each new operation
could be added separately, and the member classes were independent of the operations that
apply to them.
We can have both by packaging related operations from each class in a separate object, called
a visitor, and passing it to members of a class’s member list as it’s traversed. When an
member ‘accepts’ the visitor, it sends a request to the visitor that encodes the member’s class.
It also includes the member as an argument. The visitor will then execute the operation for
that member.
For example, a code generator that didn’t use visitors might generate source code for a
member by calling that member’s TMember.WriteInterfaceCode(Output: TStream);
abstract method. Each member would implement WriteInterfaceCode by writing
appropriate code to the output. If the generator created code using visitors, then it would
create a TInterfaceCodeVisitor object and call the AcceptVisitor method on the member
list with that visitor object as argument. Each member would implement AcceptVisitor by
calling back on the visitor: a field calls the VisitField method on the visitor, a method calls
VisitMethod. What used to be the WriteInterfaceCode operation in class TField, is now
the VisitField method call on TInterfaceCodeVisitor.
To make visitors work for more than just interface code generation, we need an abstract parent
class TMemberVisitor for all visitors of a member list. TMemberVisitor must declare a
method for each member class. An application that needs to generate HTML style output for
members, will define a new subclass of TMemberVisitor and will no longer need to add
application specific code to the member classes. The visitor pattern encapsulates the
operations.
TMemberVisitor
With the Visitor pattern, you define two class hierarchies: one for the elements being operated
on (the TMember hierarchy) and one for the visitors that define operations on the elements (the
TMemberVisitor hierarchy). You create a new operation by adding a subclass to the visitor
class hierarchy. As long as we don’t have to add new member types, we can simply add new
functionality by defining new TMemberVisitor subclasses.
Refer to [Gam+] for applicability and more examples of this highly interesting pattern.
DesignPatterns5, 32
ModelMaker 5
Implementation
The following code demonstrates the implementation of the visitor pattern applied to the
TMember example described above.
type
TMember = class (TObject)
public
procedure AcceptMemberVisitor(Visitor: TMemberVisitor); virtual;
end;
implementation
{ TMember }
procedure TMember.AcceptMemberVisitor(Visitor: TMemberVisitor);
begin
Visitor.VisitMember(Self);
end;
{ TField }
procedure TField.AcceptMemberVisitor(Visitor: TMemberVisitor);
begin
Visitor.VisitField(Self);
end;
{ TMethod }
procedure TMethod.AcceptMemberVisitor(Visitor: TMemberVisitor);
begin
Visitor.VisitMethod(Self);
end;
{ TProperty }
procedure TProperty.AcceptMemberVisitor(Visitor: TMemberVisitor);
begin
Visitor.VisitProperty(Self);
end;
{ TMemberVisitor }
procedure TMemberVisitor.VisitField(Instance: TField);
begin
end;
DesignPatterns5, 33
ModelMaker 5
To demonstrate the use of this pattern in the above example, the implementation for a simple
code generator just generating the member interface is listed below.
To really understand the visitor pattern, you might implement this example, and step through
the double dispatch mechanism: accept/visit.
unit CodeGenerators;
interface
type
implementation
uses Members;
type
TCodeGenerationVisitor = class (TMemberVisitor)
DesignPatterns5, 34
ModelMaker 5
private
FOutput: TTextStream;
public
procedure VisitField(Instance: TField); override;
procedure VisitMethod(Instance: TMethod); override;
procedure VisitProperty(Instance: TProperty); override;
property Output: TTextStream read FOutput write FOutput;
end;
{ TCodeGenerationVisitor }
procedure TCodeGenerationVisitor.VisitField(Instance: TField);
begin
Output.WriteLnFmt(' %s: %s;', [Instance.Name, Instance.DataName]);
end;
{ TCodeGenerator }
procedure TCodeGenerator.Generate(Members: TList; Output: TTextStream);
var
I: Integer;
begin
{ write the class definition }
Output.WriteLine('TSample = class (TObject)');
DesignPatterns5, 35
ModelMaker 5
1. In the Classes view, create the class to be visited, TMember in the motivation example, and
perhaps it’s descendant classes.
2. In the Classes view, create a visitor class, TMemberVisitor in the motivation example,
containing no members.
3. Select the class to be visited, to provide an initial context to the pattern.
4. In the Patterns view, click the button ‘Add Visitor Pattern’.
5. The visitor editor dialog will appear, in which you define the participant classes.
6. When you click OK, the pattern will be applied.
Usually the visitor class is not a descendant of the visited class. Note that it is possible to
define visitor and visited class to be the same class (although this does not seem practical).
In the visitor hierarchy, the pattern reflects changes only to (abstract) visitor class. Using
inheritance restricted VisitXXX methods in Visitor descendants will assure that visitor
descendants are automatically updated whenever the abstract visitor class gets updated. Do
this for example using the method override wizard.
If you edit the pattern from the patterns view, you may select a new combination for visitor
and visited classes. The existing pattern related methods will be removed, and new methods
will be inserted according to the new settings. The classes descending from the original visitor
will remain unaffected.
If you delete the pattern, or it is invalidated because you deleted either the visitor or visited
class, the pattern will remove any remaining pattern related code. Here too, the visitor
descendant classes will remain unaffected.
DesignPatterns5, 36
ModelMaker 5
Observer pattern
Origin
ModelMaker’s Observer pattern is based on the Observer pattern as described in
[Gam+, pages 293..303]. The implementation in Delphi’s Object Pascal language is ours.
Intent
‘Define a one-to-many dependency between objects so that when one object changes state, all
its dependants are notified and updated automatically’ [Gam+, p293]
Motivation
‘A common side-effect of partitioning a system into a collection of co-operating classes, is the
need to maintain consistency between related objects. You don’t want to achieve consistency
by making the classes tightly coupled, because that reduces their reusability’ [Gam+, p293].
Delphi’s events (which are actually method pointers) let you deal with this problem in a
structured manner. Events let you decouple classes that need to co-operate. For example: The
TButton.OnClick event is dispatched ‘to whom it may concern’, the button does not store a
(typed) reference to the class handling the event. In fact the event might not even be handled
at all. In terms of the observer pattern the object dispatching an event is called subject, the
object handling the event is called observer.
So Delphi’s events take care of decoupling classes, but what if you want to handle an event in
more than one place? ‘An observer pattern describes how to establish one-to-many
notifications. A subject may have any number of observers. All observers are notified
whenever the subject undergoes a change in state (such as a button being clicked). In response
each observer may query the subject to synchronise its state with the subject’s state’
[Gam+, p 294].
‘This kind of interaction is also known as publish-subscribe, the subject is the publisher of
notifications. It sends out these notifications without having to know who it’s observers are.
Any number of observers can subscribe to receive notifications’ [Gam+, p 294].
Implementation
The implementation of the observer pattern is taking advantage of Delphi’s events to deal
with decoupling classes. The one-to-many aspect is implemented by registering and un-
registering dedicated observers. The one-to-many mechanism is actually implemented by
iterating over the list of observers. A pleasant side-effect of ModelMaker’s Observer Pattern is
that it will create an observer class which is a TComponent descendant that can be added to the
component palette. This allows you to use the Object Inspector to create event handlers for the
observers.
DesignPatterns5, 37
ModelMaker 5
Let’s assume you’ve got a class TSubject which defines useful behaviour. The following
code demonstrates the implementation of the observer pattern.
type
TSubject = class (TObject)
private
FObservers: TList;
public
procedure RegisterObserver(Observer: TSubjectObserver);
procedure UnregisterObserver(Observer: TSubjectObserver);
end;
In this interface:
• A registration mechanism has been added to the class TSubject, consisting of:
♦ FObservers: TList; which stores all registered observers.
♦ RegisterObserver(..), which registers an observer by adding it to FObservers.
♦ UnregisterObserver(..), which unregisters an observer by removing it from
FObservers.
• A new class Observer patternhas been created: TSubjectObserver
♦ This class is a TComponent descendant.
♦ It has an Enabled property which allows you to switch the observer on and off rather
than having to register / unregister it each time. How this property actually cooperates in
the one-to-many event dispatch mechanism will be explained shortly.
As you see in the implementation: this deals only with the registration part of the observer
pattern. Now you may ask: ‘where is my one-to-many notification’?. Well: it’s not possible to
implement this as part of the pattern. The actual one-to-many notifications you have to
implement yourself. Assume that TSubject has a method Change which notifies all it’s
registered observers of a change. The observers would have an OnChange event property
which is actually dispatched. You could implement this like:
type
TSubject = class (TObject)
private
FObservers: TList;
protected
» procedure Change; { Call this method to dispatch change }
public
procedure RegisterObserver(Observer: TSubjectObserver);
DesignPatterns5, 38
ModelMaker 5
implementation
procedure TSubject.Change;
var
» Obs: TSubjectObserver;
» I: Integer;
begin
» for I := 0 to FObservers.Count - 1 do
» begin
» Obs := FObservers[I];
» if Obs.Enabled then Obs.Change;
» end;
end;
procedure TSubjectObserver.Change;
begin
» if Assigned(FOnChange) then FOnChange(Self);
end;
You might wonder why the actual one-to-many mechanism is not part of the pattern? That’s
because the implementation if not always a 100% obvious. And only 100% obvious
implementations can be implemented using patterns, because a pattern owns the sections of
code it creates. There is no way you can correct or adjust the code. But don’t worry: there’s a
wizard to help you out.
DesignPatterns5, 39
ModelMaker 5
This wizard is demonstrated in Patterns example, Transforming events using the Observer
Wizard, page 49.
If you remove the pattern, the registration mechanism and the observer class will be removed.
Be careful doing this, since the observer class may contain code you added yourself.
If you delete the subject class, the pattern will invalidate itself, and remove the observer class
too. The observer class is owned by the pattern, and can only be deleted by deleting (or
invalidating) the pattern.
Origin
ModelMaker’s Observer pattern is based on the Lock pattern.
DesignPatterns5, 40
ModelMaker 5
Intent
The pattern is used to control the life cycle of an object through reference counting. As long as
the object is referenced it should be available. When it is no longer referenced, it should
destroy itself.
Implementation
The Reference Count pattern’s implementation is basically the same as the Lock pattern. Only
the method names have been predefined and are fixed. The patterns is applied on the currently
selected class, like the Lock pattern. The SetReferenced method implements a default
behaviour which is non-pattern owned so you can edit it if you want a different behaviour.
The Reference Count pattern can be used in cooperation with the singleton pattern.
DesignPatterns5, 41
ModelMaker 5
Patterns example
Introduction
This demo will guide you through the application of three patterns: the singleton pattern, the
observer pattern and the wrapper pattern. All patterns will act on the same class structure, so it
is necessary to work through this demo step by step. You should have read the chapter Design
Patterns in order to fully understand what’s going on. That chapter gives an overview of how
to use patterns and the pattern specific details are described for each pattern. You should also
be fairly familiar with the basic principles of creating classes with ModelMaker as described
in the User Manual: you should be able to create a (placeholder) class, create a unit and
generate a source file.
After finishing the complete dome you’ll be amazed how much code was automatically
generated, implementing powerful features.
• StartProgress and EndProgress methods which allow to mark the beginning and end of
a time consuming process.
DesignPatterns5, 42
ModelMaker 5
• OnProgressStart and OnProgressFinish events which are dispatched each time a time
consuming process started. These events will typically be wired to event handlers in the
GUI to make progress display controls visible.
• The properties Progress and Description, defining the progress state. These are typically
set by the time consuming process.
• OnProgressChange and OnDescriptionChange events which are dispatched each time
one of the corresponding properties changes. These events will typically be wired to event
handlers in the GUI to update progress display controls.
• A method Abort which allows (the user) to abort a process, and a property Aborted which
the running process can check.
type
TProgress = 0..100;
TProgressor = class (TObject)
private
FAborted: Boolean;
FBusy: Boolean;
FDescription: string;
FOnDescriptionChange: TNotifyEvent;
FOnProgressChange: TNotifyEvent;
FOnProgressFinish: TNotifyEvent;
FOnProgressStart: TNotifyEvent;
FProgress: TProgress;
protected
procedure DescriptionChange;
function GetAborted: Boolean;
procedure ProgressChange;
procedure ProgressFinish;
procedure ProgressStart;
procedure SetDescription(const Value: string);
procedure SetProgress(Value: TProgress);
public
procedure Abort;
procedure EndProgress;
procedure StartProgress(AProgress: TProgress; const ADescription: string);
property Aborted: Boolean read GetAborted;
property Busy: Boolean read FBusy;
property Description: string read FDescription write SetDescription;
property OnDescriptionChange: TNotifyEvent read FOnDescriptionChange write
FOnDescriptionChange;
property OnProgressChange: TNotifyEvent read FOnProgressChange write
FOnProgressChange;
property OnProgressFinish: TNotifyEvent read FOnProgressFinish write
FOnProgressFinish;
property OnProgressStart: TNotifyEvent read FOnProgressStart write
FOnProgressStart;
property Progress: TProgress read FProgress write SetProgress;
end;
procedure TSomeClass.DoSomething;
var I: TProgress;
begin
Progressor.StartProgress(0, ‘Doing something’);
for I := 1 to 100 do
begin
{ do something }
Progressor.Progress := I;
if Progressor.Aborted then Break;
end;
DesignPatterns5, 43
ModelMaker 5
Progressor.EndProgress;
end;
In this demo we won’t focus on features of the progressor class, nor on the implementation of
the methods.
DesignPatterns5, 44
ModelMaker 5
6. Add a property ‘Description’ of data type string. Select for read access a field
(FDescription will be created) and for write access a method (SetDescription will be
created). Click OK. We will use SetDescription to dispatch the OnDescriptionChange
event.
7. Add a read only property ‘Busy’ of data type Boolean. Select for read access a field (FBusy
will be created) and for write access ‘none’ (read only access). Click OK. This property
keeps track of the ‘busy’ state. It will be set internally when StartProgress and
EndProgress are called.
8. Add a read only property ‘Aborted’ of data type Boolean. Select for read access a method
(GetAborted will be created) and for write access ‘none’ (read only access). Also set the
option ‘State Field’, this will create the FAborted field . Click OK. The field FAborted
will keep track of the ‘user aborted’ state. It will be set internally when StartProgress or
Abort is called. The method GetAborted will check FAborted and the Application’s
property Terminated.
9. Now add the events ‘OnProgressChange’, ‘OnDescriptionChange’, ‘OnProgressStart’
and ‘OnProgressFinish’ all of event type TNotifyEvent. For all events set the options
‘Dispatch method’ and use the default dispatch method name by clicking the hand-down
button next to the event name edit. Also make all visibilities public since we have no
RTTI available for this class (or you must enable the $M+ compiler switch).
10.Now add the public interface methods: Abort, StartProgress and EndProgress
according to the above described interface. Don’t implement them yet.
At this stage your complete interface should be defined. Create a new unit (for example
PATDEMO.PAS and add this class to the unit, generate the unit and check the interface in
Delphi. You must manually add the ‘type TProgress = 0..100;’ declaration to the unit’s
interface, remember to do this in ModelMaker!
Now finish implementing the class described below, using the Method code editor, and
regenerate your unit: (Lines marked with a » have to be added manually)
procedure TProgressor.Abort;
begin
» FAborted := True;
end;
procedure TProgressor.DescriptionChange;
begin
if Assigned(FOnDescriptionChange) then FOnDescriptionChange(Self);
end;
procedure TProgressor.EndProgress;
begin
» FBusy := False;
» ProgressFinish;
DesignPatterns5, 45
ModelMaker 5
end;
procedure TProgressor.ProgressChange;
begin
if Assigned(FOnProgressChange) then FOnProgressChange(Self);
end;
procedure TProgressor.ProgressFinish;
begin
if Assigned(FOnProgressFinish) then FOnProgressFinish(Self);
end;
procedure TProgressor.ProgressStart;
begin
if Assigned(FOnProgressStart) then FOnProgressStart(Self);
end;
Yes, it’s obvious that this class is not intended for multi-level progressing since
StartProgress and EndProgress cannot be nested. We’ll leave implementing that feature
for you, just as updating the screen’s cursor and blocking the screen’s user interface to avoid
re-entrancy etc.
DesignPatterns5, 46
ModelMaker 5
Congratulation, you just have applied the singleton pattern. It’s as easy as that.
You might want to check the code in Delphi’s editor and compare it with the code as
described in Singleton pattern, page 18, which also describes the purpose of all inserted
methods.
The main member the singleton pattern inserted is the class method Instance. This method
now provides the central access point to the TProgressor class. It also takes care of creating
an instance the first time it is called.
The only thing there remains to do manually, is clean up the instance when the application
terminates. In Delphi 1.0 you do this by adding an exit procedure in the unit’s initialization
section and implementing this exit procedure:
unit <!UnitName!>;
...
...
implementation
...
...
...
initialization
MMWIN:START INITIALIZATION
DesignPatterns5, 47
ModelMaker 5
unit <!UnitName!>;
...
...
implementation
...
...
...
initialization
MMWIN:START INITIALIZATION
MMWIN:CLASS INITIALIZATION ;ID=412;
finalization
{ This will free the instance, if we had one.
Do NOT call TProgressor.Instance.Free, since that will create
the instance, even if it never had been created before }
» TProgressor.ReleaseInstance;
end.
For a test application, create a new project with a Form called MainForm. And put a TButton
called Btn and a TGauge called Gauge on this form. Then add the following code:
In the FormCreate method the TProgressor.Instance is called the first time. This will
instantiate the single instance. Subsequent calls to TProgressor.Instance will return the
same instance each time, for example in the BtnClick event handler.
DesignPatterns5, 48
ModelMaker 5
• Hard-code a chain of notifications by calling the next object to be updated from the object
handling the TProgressor events. This creates a lot of interdependency.
• Use a newly invented registration or hook structure which passes on events like, for
example,. the Windows clipboard chain. This will take some time to implement and debug.
• Use an observer pattern to create one-to-many notifications which is a standard way of
solving design problems like this, avoiding unnecessary interdependency and complexity.
DesignPatterns5, 49
ModelMaker 5
• Apply an observer pattern to the TProgressor class. This will create a new (observer)
class TProgressorObserver, including registration mechanisms in TProgressor.
• Transform the events in the existing TProgressor class into one-to-many events
dispatched by the TProgressorObserver class.
The observer pattern has now been applied and the events have been transformed.
Now have a look at the TProgressorObserver class’s members and notice that all
transformed events have been added to the observer class including their corresponding
dispatch methods. Also an Enabled property has been added. This property let’s you switch
the observer on and off rather than having to register / unregister it each time. The observed
class TProgressor checks this property in its dispatched methods, like
TProgressor.Progresschange.
DesignPatterns5, 50
ModelMaker 5
Another nice feature to add is the automatic (un) registration of the observers. Since the
TProgressor class is a singleton, there is only one instance to register with, so why not do it
automatically in the observer’s Create and unregister again in Destroy? You’ll need to
override TComponent’s Create and Destroy methods using the method override wizard.
Make sure to check the option ‘Call inherited method’ when you do this. Here’s the
resulting code:
constructor TProgressorObserver.Create(aOwner: TComponent);
begin
inherited Create(aOwner);
» TProgressor.Instance.RegisterObserver(Self);
end;
destructor TProgressorObserver.Destroy;
begin
» TProgressor.Instance.UnregisterObserver(Self);
inherited Destroy;
end;
A final detail is to make the observer’s events have published visibility to make them
available in the Object Inspector. Published since that was their original visibility in the
TProgressor class.
After you compiled your test project or checked it’s syntax, you’re ready to install the unit in
the component lib. After this has been done, you should have the TProgressorObserver
class available.
Of course you would test this class too. For example by using the same test project as before,
but now by adding two observers to a single form and wiring some events. Some especially
interesting items to step through with the debugger are:
DesignPatterns5, 51
ModelMaker 5
Wrapping TProgressor
As you find in the description of the wrapper pattern, in the wrapping class it needs a
reference to the class to wrap. So the first step will be to provide that reference. We’ll create a
property Progressor which accesses the TProgressor.Instance class method.
To do this:
1. In the Classes view, select the TProgressorObserver class.
2. In the Class Members view, add a property ‘Progressor’ of data type ‘class’.
♦ Select the TProgressor class as Data name.
♦ Select for read access a method (GetProgressor will be created) and for write access
none (read only access)
♦ Click OK.
3. Implement the GetProgressor method to call TProgressor.Instance.
DesignPatterns5, 52
ModelMaker 5
• Properties Progress and Description, together with their access methods GetXXX, and
SetXXX. The access methods simply read or write the Progressor.XXX properties.
Have a look in the Delphi code editor, to see how much has actually been inserted by the three
patterns. You’ll see that apart from the automatic registration in Create and Destroy, the
class TProgressorObserver is completely generated by patterns or other automatic code!
Let’s see what happens if we change the parameter list in method StartProgress in the
observed class TProgressor. Select TProgressor.StartProgress in the Class Members
view. Edit it’s attributes (double click it) and clear the parameter list. Notice how the pattern
will reflect this change by clearing the parameter list in method
TProgressObserver.StartProgress and in the implementation which calls to
Progressor.StartProgress. Something similar will happen if you’d change the
StartProgress method in to a function returning a Boolean. Do this now and notice how the
pattern updates TProgressObserver.StartProgress’s declaration and implementation.
Restore the original situation again.
Assume now you want to ‘unwrap’ the StartProgress method. To do this, select the
Wrapper pattern in the Patterns view, and double click it. The wrapper editor will appear
again. Move the StartProgress method back to ‘available members’ list on the left and
Click OK. Notice how the pattern has removed the StartProgress method from
TProgressObserver. To wrap this method again, simple start the Wrapper editor again from
the Patterns view and add it again.
Delete the Singleton pattern first. (If you want to delete a pattern, click the ‘Delete pattern’ in
the Patterns view.) Notice how all methods related to that pattern are removed from the
TProgressor class. Of course the clean up code we added manually is not removed and yes:
the code won’t compile anymore, since the TProgressor.Instance method which is needed
in TProgressorObserver has been removed too. You could of course simply apply the
singleton pattern again to restore the code. But we’ll leave that exercise to you.
Now let’s do something more interesting: invalidate a pattern. The wrapper pattern needs a
reference to the class it wraps. In our case we choose the
TProgressorObserver.Progressor property. What if we deleted that property? Let’s find
out and delete it from the class. Select the property progressor and click the ‘Delete
members’ button in the Class Members view. Now that cleans up a bit: as soon as the pattern
DesignPatterns5, 53
ModelMaker 5
notices it’s reference is deleted, it starts deleting all wrapped members (Abort, Progress etc).
If you look at the patterns view, you’ll see that the pattern has become INVALID. You must
remove this pattern now manually. Some other patterns may be re-validated, the wrapper
pattern not. Now delete the invalidated wrapper pattern.
At this stage only the observer pattern is left. You could invalidate this by deleting the
TProgressorObserver class, but instead you can delete the pattern straight away. If you have
done this, notice how the class TProgressorObserver is deleted and how the registration
mechanism has been removed from TProgressor. We are back to were we started from. The
only exception is that the Observer Wizard, which is not a part of the pattern, converted event
into iterations, this is all there remains from our pattern experiments. You could remove the
converted dispatch methods and manually add events again.
DesignPatterns5, 54
ModelMaker 5
Bibliography
[Gam+] Addison Wesley, Erich gamma, Richard Helm, Ralph Johnson, John Vlissides,
Design Patterns, Elements of Reusable Object-Oriented Software. 1995.
ISBN 0-201 -63361-2.
[CWG1] Delphi 1.0 Component writers guide, Borland
[CWG2] Delphi 2.0 Component writers guide, Borland
[DUG1] Delphi 1.0 User guide, Borland
[DUG2] Delphi 2.0 User guide, Borland
DesignPatterns5, 55