Sei sulla pagina 1di 29

Chapter 10 Two Patterns

Using Composition

Main concepts

– Using delegation (in the form of composition or association) in implementing patterns


– Using the Adapter pattern to:
– encapsulate and adapt legacy objects or software, and
– to extend polymorphism to classes outside a particular hierarchy.
– Object and class Adapters
– Using the Facade pattern to:
– decrease system coupling and to increase cohesion, and
– to simplify interfacing.
– Fabricating an intermediate class between other classes.

Chapter contents

Delegation: association and composition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2


Example 10.1 The Adapter Pattern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
Ex 10.1 step 1 A legacy class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
Ex 10.1 step 2 The Adapter class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
Ex 10.1 step 3 Testing the adapter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
Ex 10.1 step 4 A complex Adapter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
Ex 10.1 step 5 Testing the Assign method . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9

Two Patterns Using Composition (3 Oct 2006) Chapter 10, Page 1


Example 10.2 The Adapter pattern for polymorphism . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
Ex 10.2 step 1 The legacy code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
Ex 10.2 step 2 The Adapter class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
Ex 10.2 step 3 Using the new class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
Another kind of Adapter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
Example 10.3 A Class Adapter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
Ex 10.3 step 1 TNewCounter as a class adapter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
Ex 10.3 step 2 Hiding the superclass’s methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
Class Adapter for polymorphism . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
Pattern 10.1 The (Object) Adapter pattern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
Example 10.4 Introducing the Facade Pattern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
Ex 10.4 step 1 Creating an input form . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
Ex 10.4 step 2 Using the input form . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
Example 10.5 The Facade . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
Ex 10.5 step 1 Implementing the Facade . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
Ex 10.5 step 2 Using TMyFacade . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
Pattern 10.2 The Facade Pattern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
Chapter 10 summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
Problems . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27

Delegation: association and composition

The previous two chapters have introduced us to the concepts of association, composition
and delegation. Briefly stated, we write a new class that refers to or incorporates an instance
of another class, in the sense of carrying a link to it, and then calls its methods to use some
or all of that class’s functionality without having to re-implement it.
By allowing one to incorporate the facilities of an existing class into a new class,
delegation has some similarity to inheritance. However, unlike inheritance, each required
facility must be exposed explicitly. While this requires more work on the part of the
programmer, it also allows facilities of the existing class to be kept hidden, something not
possible through inheritance.
If a single class delegates operations to more than one existing class, an effect similar to
multiple inheritance is possible while avoiding some of the complications.
Delegation is an important OO principle and is used in a variety of patterns such as the
Adapter, the Facade and the Strategy patterns. In this chapter we look at the Adapter and
Facade patterns.

Chapter 10, Page 2 Object orientation with Delphi (all rights reserved)
Example 10.1 The Adapter Pattern

A composed object is the owner of constituent objects that it can use while hiding them from
others. This opens up the possibility of adapting legacy software or existing classes for
reuse. When looking for reuse of existing software in a new application we may find a class
or a non-OO program that performs the operations we need but that does not meet some
other requirement or that may be subject to future change. Adapting accommodates these
factors by encapsulating this existing software as an object and limiting its coupling with the
rest of the program.
This example presents the Adapter Pattern as a way of reusing legacy software within an
OO programming approach. The next example shows how an object in one hierarchy may
be adapted to another inheritance hierarchy when this is required for polymorphism.

Ex 10.1 step 1 A legacy class

Assume that we are programming to a standard that requires separate access methods for
modifying and for reading object data, and that these methods must work with a consistent
data type. We have an existing TCounter class that does not meet these standards. First, its
access methods (the methods AddAndRead and ClearAndRead below) both modify the
value of the field data and return a value, which is not good programming practice. Second,
its access methods accept an integer and return a string. According to the standard these
should be of the same type. We want to reuse this class but we don’t want to ignore our
standards, so we can create an adapter with the required interface. The existing class is:

1 unit CounterU;

2 interface

3 type
4 TCounter = class (TObject)
5 protected // allows subclasses to access this data field
6 Total: Integer;
7 public
8 function AddAndRead (Number: Integer): string;
9 function ClearAndRead: string;
10 end; // end TCounter = class (TObject)

11 implementation
12
13 uses
14 SysUtils;

Two Patterns Using Composition (3 Oct 2006) Chapter 10, Page 3


15 function TCounter.AddAndRead (Number: Integer): string;
16 begin
17 Inc (Total, Number);
18 Result := IntToStr (Total);
19 end; // end procedure TCounter.AddAndRead

20 function TCounter.ClearAndRead: string;


21 begin
22 Total := 0;
23 Result := IntToStr (Total);
24 end; // end procedure TCounter.ClearAndRead

25 end. // end unit CounterU

Ex 10.1 step 2 The Adapter class

We create a composed class that has the method signatures required by the standards (the
methods Add, Clear and GetValue) and that adapts these to calls to the legacy class:

1 unit NewCounterU;

2 interface

3 uses
4 CounterU; // contains the adapted class

5 type

6 TNewCounter = class(TObject)
7 private
8 FOldCounter: TCounter; // the adaptee, kept private
9 public
10 // composition: must propagate create and destroy
11 constructor Create;
12 destructor Destroy; override;
13 // adapter methods providing the required interface
14 procedure Add (ANumber: integer);
15 procedure Clear;
16 function GetValue: integer;
17 end; // end TNewCounter = class(TObject)

18 implementation

19 uses SysUtils;

20 { TNewCounter }

21 procedure TNewCounter.Add(ANumber: integer);


22 begin
23 FOldCounter.AddAndRead (ANumber); // ignore the return value
24 end; // end procedure TNewCounter.Add

Chapter 10, Page 4 Object orientation with Delphi (all rights reserved)
25 procedure TNewCounter.Clear;
26 begin
27 FOldCounter.ClearAndRead; // ignore the return value
28 end; // end procedure TNewCounter.Clear

29 constructor TNewCounter.Create;
30 begin
31 inherited;
32 FOldCounter := TCounter.Create; // propagate
33 end; // end constructor TNewCounter.Create

34 destructor TNewCounter.Destroy;
35 begin
36 FOldCounter.Free; // propagate
37 inherited;
38 end; // end destructor TNewCounter.Destroy

39 function TNewCounter.GetValue: integer;;


40 begin
41 Result := StrToInt(FOldCounter.AddAndRead(0)); //dummy add to read
42 end; // end function TNewCounter.GetValue

43 end. // end unit NewCounterU

We now have two procedure methods for manipulating the data (lines 21–28) and a function
for getting the value (lines 39–42), and so comply with Delphi programming convention as
well as the more general convention that separate methods be used for reading and writing
object data. Add and GetValue also both involve an integer. (GetValue uses StrToInt for the
conversion.)
The Adapter Pattern relies on composition, and so this class uses an explicit constructor
and explicit destructor to propagate object creation and destruction (lines 29–38).

Ex 10.1 step 3 Testing the adapter

We use a simple driver program to test the adapter (figure 1).

Figure 1 Testing the


TNewCounter adapter

Two Patterns Using Composition (3 Oct 2006) Chapter 10, Page 5


Figure 2 Objects on the driver
interface

1 unit AdapterDemoU;

2 interface

3 { standard RAD preamble }

21 implementation

22 uses NewCounterU;

23 var
24 Items: TNewCounter; // the adapter

25 {$R *.DFM}

26 procedure TfrmItems.btnAddClick(Sender: TObject);


27 begin
28 Items.Add (sedNewItems.Value);
29 lblTotalItems.Caption := 'Value is ' + IntToStr (Items.GetValue);
30 end; // end procedure TfrmItems.btnAddClick

31 procedure TfrmItems.btnClearClick(Sender: TObject);


32 begin
33 Items.Clear;
34 lblTotalItems.Caption := 'Value is ' + IntToStr (Items.GetValue);
35 end; // end procedure TfrmItems.btnClearClick

36 initialization
37 Items := TNewCounter.Create;

38 end. // end AdapterDemoU

This program works well. It uses TNewCounter, the adapter class (lines 22, 24), and does
not know that these method calls are delegated to an instance of TCounter. Thus
encapsulation is maintained and all the coupling is between neighbouring classes. As
suggested by the Law of Demeter, TfrmItems knows only about TNewCounter, which in
turn knows only about TCounter. TCounter knows about either of the other classes (figure
3).

Chapter 10, Page 6 Object orientation with Delphi (all rights reserved)
Figure 3 TNewCounter as an object adapter for TCounter

Ex 10.1 step 4 A complex Adapter

The class given in step 2 is a simple adapter. It stores no local data and performs no local
manipulations. This is the goal of the Adapter Pattern, but it can lead to clumsiness and/or
inefficiency. For example, the Get... method above requires a clumsy implementation (step
2, line 41). Assuming that we can’t modify the legacy class (the adaptee, TCounter) we may
decide to create a local variable in the adapter class to store the value locally.
There are also situations where the adapted class does not provide all of the required
functionality, and then we need to implement these in the adapter. For example, in keeping
with the previous chapter, this adapter should have an Assign method. So this new version
of the adapter is derived from TPersistent. (For brevity, we won’t implement the AssignTo
here.)

1 unit NewCounterU;

2 interface

3 uses Classes, CounterU;

4 type

5 TNewCounter = class(TPersistent)
6 private
7 FOldCounter: TCounter; // the adaptee
8 FValue: string; // data local to the adapter
9 public
10 procedure Assign (ANewCounter: TPersistent); override;
11 constructor Create;
12 destructor Destroy; override;
13 // adapter methods
14 procedure Add (ANumber: integer);
15 procedure Clear;
16 function GetValue: integer;
17 end; // end TNewCounter = class(TPersistent)

18 implementation

19 uses SysUtils;

Two Patterns Using Composition (3 Oct 2006) Chapter 10, Page 7


20 { TNewCounter }

21 procedure TNewCounter.Add(ANumber: integer);


22 begin
23 FValue := FOldCounter.AddAndRead (ANumber);
24 end; // end procedure TNewCounter.Add

25 procedure TNewCounter.Assign (ANewCounter: TPersistent);


26 begin
27 if ANewCounter is TNewCounter then
28 FValue := IntToStr(TNewCounter(ANewCounter).GetValue)
29 else
30 inherited Assign (ANewCounter); // deep copy failed
31 end; // end procedure TNewCounter.Assign(aCustomer: TPersistent)

32 procedure TNewCounter.Clear;
33 begin
34 FValue := FOldCounter.ClearAndRead;
35 end; // end procedure TNewCounter.Clear

36 constructor TNewCounter.Create;
37 begin
38 inherited;
39 FOldCounter := TCounter.Create; // propagate
40 end; // end constructor TNewCounter.Create

41 destructor TNewCounter.Destroy;
42 begin
43 FOldCounter.Free; // propagate
44 inherited;
45 end; // end destructor TNewCounter.Destroy

46 function TNewCounter.GetValue: integer;


47 begin
48 Result := StrToInt(FValue);
49 end; // end function TNewCounter.GetValue

50 end. // end unit NewCounterU

We declare the local variable in line 8, and set it in lines 23 and 34. Then in the Get... method
we simply read the local variable (line 48). In the next step we’ll change the user interface to
test the Assign method.

Chapter 10, Page 8 Object orientation with Delphi (all rights reserved)
Ex 10.1 step 5 Testing the Assign method

Figure 4 Testing the Assign


method

To test the Assign method, add two buttons, called btnCopy and btnShowCopy, to the
existing user interface (figure 4), declare an additional reference ItemsCopy to a
TNewCounter and add the buttons’ OnClick event handlers and the instantiation for
ItemsCopy:

procedure TfrmItems.btnCopyClick(Sender: TObject); // new


begin
ItemsCopy.Assign(Items);
btnShowCopy.Click; // raise an OnClick event by software
end; // end procedure TfrmItems.btnCopyClick

procedure TfrmItems.btnShowCopyClick(Sender: TObject); // new


begin
lblTotalItems.Caption := 'Copy value is ' +
IntToStr (ItemsCopy.GetValue);
end; // end procedure TfrmItems.btnShowCopyClick

initialization
Items := TNewCounter.Create;
ItemsCopy := TNewCounter.Create; // new

Example 10.2 The Adapter pattern for polymorphism

In examples 6.1 & 6.3 we demonstrated polymorphism through a hierarchy of different


types of furniture. For this example we extend that hierarchy to incorporate an existing class,
TTheCupboard. How can we persuade an existing class to operate polymorphically within a
different hierarchy?
We’re discussing the Adapter here, so you have probably guessed that an Adapter will
solve this problem. In figure 5 we have added the TCupboard class as an adapter to allow
the reuse of the existing TTheCupboard class.

Two Patterns Using Composition (3 Oct 2006) Chapter 10, Page 9


Figure 5 Adding TCupboard to the hierarchy of example 6.3 as an object adapter
for TTheCupboard

Ex 10.2 step 1 The legacy code

The existing code we are reusing is the following:

1 unit TheCupboardU;

2 interface

3 type
4 TTheCupboard = class (TObject)
5 public
6 function HuRU: string;
7 end; // TTheCupboard = class (TObject)

8 implementation

9 { TTheCupboard }

10 function TTheCupboard.HuRU: string;


11 begin
12 Result := 'A Cupboard';
13 end; // end function TTheCupboard.HuRU: string;

14 end. // end unit TheCupboardU

Two factors prevent us from using this code as it is in the program of example 6.3. It’s not
part of the TFurniture hierarchy so it can’t participate in the polymorphic method call

Chapter 10, Page 10 Object orientation with Delphi (all rights reserved)
(example 6.3 step 2, line 39), and the method has the wrong name (HuRU, which some crazy
programmer thought was a cool abbreviation for ‘Who are you?’, instead of GetKind).

Ex 10.2 step 2 The Adapter class

The adapter pattern suggests that we create a composed class, TCupboard within the existing
TFurniture hierarchy, to delegate its operations to the consituent TTheCupboard class
shown above, which is not part of the TFurniture hierarchy.
Since all the classes in the TFurniture hierarchy are very small, we have placed them all
in the same unit. With more complex classes they would be in separate units.

1 unit FurnitureU;

2 // A polymorphic adapter

3 interface

4 uses
5 TheCupboardU; // unit containing the adaptee

6 type

7 { TFurniture ... TKitchenTable definitions as example 6.3, step 1 }

27 TCupboard = class (TFurniture) // Adapter


28 private
29 FOtherCupboard: TTheCupboard; // The adaptee
30 public
31 constructor Create; // propagate create
32 destructor Destroy; override; // propagate destroy
33 function GetKind: string; override;
34 end; // TCupboard = class (TFurniture)

35 implementation

36 { TFurniture ... TKitchenTable methods as example 6.3, step 1 }

61 { TCupboard }

62 constructor TCupboard.Create;
63 begin
64 inherited;
65 FOtherCupboard := TTheCupboard.Create;
66 end; // end constructor TCupboard.Create

67 destructor TCupboard.Destroy;
68 begin
69 FOtherCupboard.Free;
70 inherited;
71 end; // end destructor TCupboard.Destroy

Two Patterns Using Composition (3 Oct 2006) Chapter 10, Page 11


72 function TCupboard.GetKind: string;
73 begin
74 Result := FOtherCupboard.HuRU + ', ' + inherited GetKind;
75 end; // end function TCupboard.GetKind

76 end. // end Unit FurnitureU

As always with a composed class, we propagate Create (lines 62–66) and Destroy (lines
67–71). Delegating the responsibility is a breeze (line 74) and allows us to adapt the method
name as well. Notice that the delegating method is still declared as an override method, like
the other GetKind methods (line 33). We don’t implement an Assign method because
TFurniture has no data fields.

Ex 10.2 step 3 Using the new class

Figure 6 Testing the


TCupboard Adapter

Because we have been able to maintain the polymorphism through the adapter, including
the cupboard in our driver program is simple. We add it as an Item after Chair in the
ComboBox list and then add a line to the Case statement in the OnClick event handler (line
30), adjusting the other Case indexes as necessary (cf example 6.3, step 2, lines 24–36). We
need make no change to btnKind’s OnClick event handler (example 6.3, step 2, lines 37–43).
Because we reset the MyFurniture reference in line 26, we don’t need an else clause in the
case statement to catch invalid values of ItemIndex.

24 procedure TfrmSubstitution.rgbFurnitureClick(Sender: TObject);


25 begin
26 FreeAndNil (MyFurniture);
27 case rgbFurniture.ItemIndex of
28 0: MyFurniture := TFurniture.Create;
29 1: MyFurniture := TChair.Create; // 5 substitutions
30 2: MyFurniture := TCupboard.Create;
31 3: MyFurniture := TTable.Create;
32 4: MyFurniture := TCoffeeTable.Create;

Chapter 10, Page 12 Object orientation with Delphi (all rights reserved)
33 5: MyFurniture := TKitchenTable.Create;
34 end;
35 lblKind.Caption := '';
36 end; // end procedure TfrmSubstitution.rgbFurnitureClick

Here we see that an adapter class can be used polymorphically within its inheritance
hierarchy. The adaptee (the adapted class) does not operate polymorphically through its
inheritance hierarchy when accessed via the adapter.

Another kind of Adapter

The adapter in the examples above is an object adapter, and is implemented through
composition. Sometimes, and more often in languages like C++ that allow multiple
inheritance, one also sees a class adapter, implemented through inheritance.
In view of the cautions raised in chapter 7 about the appropriate use of inheritance, we
generally recommend object adapters in preference to class adapters. But some important
authors, including the Gang of Four (Gamma et al, 1995) discuss class adapters and so we do
too.
If we have an existing class with the required functionality but with the wrong signature,
we can derive an adapter class from the existing class and implement the appropriate
methods in the adapter (ie the subclass) to adapt the inherited methods (figure 7). We’ll
implement this in example 10.3. It’s quite a neat solution, but in Delphi or Java, which do
not have private inheritance, it exposes the original methods. Without multiple inheritance,
it also does not allow a polymorphic adapter as in example 10.2.

Figure 7 TNewCounter as a
class adapter for TCounter

Two Patterns Using Composition (3 Oct 2006) Chapter 10, Page 13


Example 10.3 A Class Adapter

Because an adapter codes to a particular class signature and reduces coupling through
encapsulation, we should be able to take example 10.1 and change the implementation of the
adapter class without needing to change either of the other classes. We’ll see this now, in
this example, where the other two classes are not even aware that we have changed the
implementation of the TNewCounter class from an object adapter to a class adapter.

Ex 10.3 step 1 TNewCounter as a class adapter

Instead of composing TNewCounter from TCounter as in example 10.1 step 2 (or step 4), we
can alternatively subclass TNewCounter from TCounter (line 7 below). This means that we
no longer need to propagate the Create and Destroy and so this class definition is quite a bit
shorter. Because Total is declared as protected in example 10.1, step 1, lines 5–6,
TNewCounter can access its value directly in the GetValue method (line 28 below). (If Total
had been declared as private, we would have to introduce an FValue data field for
TNewCounter as in example 10.1, step 4, line 9.)
We could add an Assign method here too, but because TCounter is not derived from
TPersistent we would not be able to take advantage of TPersistent’s Assign and so would
not be able to invoke the inherited Assign.

1 unit NewCounterU;

2 // Class adapter, derived from TCounter

3 interface

4 uses
5 CounterU; // contains adapted class (the superclass)

6 type

7 TNewCounter = class(TCounter) // derived from TCounter


8 public
9 // class adapter methods
10 procedure Add (ANumber: integer);
11 procedure Clear;
12 function GetValue: integer;
13 end; // end TNewCounter = class(TCounter)

14 implementation

15 uses
16 SysUtils; // for IntToStr

Chapter 10, Page 14 Object orientation with Delphi (all rights reserved)
17 { TNewCounter }

18 procedure TNewCounter.Add(ANumber: integer);


19 begin
20 AddAndRead (ANumber);
21 end; // end procedure TNewCounter.Add

22 procedure TNewCounter.Clear;
23 begin
24 ClearAndRead;
25 end; // end procedure TNewCounter.Clear

26 function TNewCounter.GetValue: integer;


27 begin
28 Result := Total;
29 end; // end function TNewCounter.GetValue

30 end. // end unit NewCounterU

When we run this version it works as before without needing to change either of the other
two units. A potential problem here is that through the inheritance TNewCounter exposes
all of TCounter’s public methods and data, and so this implementation provides weaker
encapsulation than the composition approach of the object adapter. As we’ll see in the next
step, there is a workaround if there are good reasons for using a class adapter instead of the
object adapter as in example 10.1.

Ex 10.3 step 2 Hiding the superclass’s methods

As mentioned in chapter 7 while discussing inheritance anti-patterns, one can use a kludge
to hide the superclass’s methods and so prevent access to a superclass’s public methods
through the subclass. It is usually best avoided, but it is useful to know about it. The
approach is that for each method that must be hidden in the superclass one provides a
method in the subclass to hide the superclass method by raising an exception in the
subclass.

1 unit NewCounterU;

2 // Class adapter, derived from TCounter

3 interface

4 uses
5 CounterU; // contains adapted class

6 type

7 TNewCounter = class(TCounter) // derived from TCounter

Two Patterns Using Composition (3 Oct 2006) Chapter 10, Page 15


8 public
9 // class adapter methods
10 procedure Add (ANumber: integer);
11 procedure Clear;
12 function GetValue: integer;
13 // hiding superclass methods
14 function AddAndRead (Number: Integer): string;
15 function ClearAndRead: string;
16 end; // end TNewCounter = class(TCounter)

17 implementation

18 uses
19 SysUtils; // for exceptions

20 { TNewCounter }

21 procedure TNewCounter.Add(ANumber: integer);


22 begin
23 inherited AddAndRead (ANumber); // call inherited version
24 end; // end procedure TNewCounter.Add

25 function TNewCounter.AddAndRead(Number: Integer): string;


26 // invalidate access to superclass method
27 begin
28 raise Exception.Create('Invalid call'); // poor style
29 end; // end function TNewCounter.AddAndRead

30 procedure TNewCounter.Clear;
31 begin
32 inherited ClearAndRead; // call inherited version
33 end; // end procedure TNewCounter.Clear

34 function TNewCounter.ClearAndRead: string;


35 // invalidate access to superclass method
36 begin
37 raise Exception.Create('Invalid call'); // poor style
38 end; // end function TNewCounter.ClearAndRead

39 function TNewCounter.GetValue: integer;


40 begin
41 Result := Total; // data field declared in superclass
42 end; // end function TNewCounter.GetValue

43 end. // end unit NewCounterU

TCounter inherits two methods, AddAndRead and ClearAndRead from TCounter. To hide
these (in the interests of better encapsulation) we re-implement them in TNewCounter and
raise an exception should these ever be invoked (lines 28 & 37). The Adapter methods
introduced in TNewCounter must now call the inherited methods (lines 23 & 32) to avoid
rasing these exceptions themselves.

Chapter 10, Page 16 Object orientation with Delphi (all rights reserved)
Invalidating superclass methods as shown here is messy and error-prone, and so it is
generally better to use delegation and implement object adapters (eg examples 10.1 and
10.2) in preference to using inheritance and class adapters as in this example.

Class Adapter for polymorphism

We mentioned earlier that one can use a class adapter to make a class available in a different
hierarchy where it will be used polymorphically. This requires multiple inheritance. It is not
possible in Delphi or Java, but is possible in C++. To illustrate why this needs multiple
inheritance, figure 8 shows the object adapter of figure 4 restructured for a class adapter.
TCupboard is no longer composed of TTheCupboard but is derived both from it and from
TFurniture.

Figure 8 A class adapter to make one class available in another inheritance


hierarchy

We will leave the class adapter now and move on to look at a pattern statement for an object
adapter.

Pattern 10.1 The (Object) Adapter pattern

At times, an existing class, say ClassB in hierarchy B, provides functionality needed by


ClassA in hierarchy A. It may not be appropriate or possible to combine the two hierarchies,
for example where the ClassB’s source is not available, or where ClassB’s method signatures
are not the required ones, or where there is not a proper IsA relationship between the
classes. To reduce repetition and promote reuse, it is desirable not to recode the required

Two Patterns Using Composition (3 Oct 2006) Chapter 10, Page 17


functionality in ClassA. Because of the need for a different signature or for polymorphic
substitution, ClassB cannot be used directly.

Therefore,

have ClassA, the Adapter, delegate the required operations to ClassB, the Adaptee. The
Adapter’s delegating methods operate polymorphically in hierarchy A while the Adaptee’s
delegate methods perform the required tasks. The Adapter’s and the Adaptee’s methods
may have different signatures. The Adapter’s clients are unaware of the delegation and the
Adaptee is unaware of the Adapter. The Adapter may have to perform some local
processing and store some local data in addition to delegating the operations.
This describes the Object Adapter, often referred to simply as the Adapter. A Class
Adapter is also possible (example 10.3), but is mainly justified in languages with multiple
inheritance, and so we do not present it as a pattern here.
An Adapter is sometimes called a Wrapper, but the term Wrapper is also used in other
ways. So for clarity we use the term Adapter when referring to this pattern.

Example 10.4 Introducing the Facade Pattern

At an implementation level, the Facade pattern is very similar to the Adapter. Conceptually,
it provides a simple interface with controlled visibility to a group of objects or to a complex
object. So where the purpose of an Adapter is to adapt an object in some way or other, the
purpose of a Facade is to reduce the coupling between a set of objects (or a single complex
object) and the rest of the system. We’ll explore this by developing a Facade for a RAD-
generated user interface object.
As we saw in chapter 8, when Delphi creates a RAD form, all the data (ie the
components) and the behaviour (ie the event handlers) have published visibility so that they
can be accessed through the Object Inspector. A form is a complex grouping of objects
similar to a composed object but without controlled visibility. In chapter 8 we provided
some encapsulation by making a particular object private (example 8.5 step 1 line 12) and
then writing an access method for just the property we were interested in. The Facade
pattern offers an alternative approach. It is simpler and more elegant in the sense that we do
not have to modify a RAD generated form to hide components. Instead, we create an
intermediate object, the Facade, which, through delegation, provides access only to the
required methods and data.

Chapter 10, Page 18 Object orientation with Delphi (all rights reserved)
Consider an application where we need to prompt the user for two string inputs. We
could make two consecutive calls to InputBox, getting each input separately, but we’d prefer
both inputs on the same Form (figure 9):

Figure 9 Prompting the user


for two input fields

We’ll consider the ‘typical’ way to do this and the accompanying weaknesses as a way of
introducing the Facade.

Ex 10.4 step 1 Creating an input form

Start a new application and add a second form. This will be the (RAD generated) input
Form (figures 9 & 10). Set the BitBtn bmbCancel’s Kind property to bkCancel and its
ModalResult property to mrCancel. Clicking this button then automatically closes the form
without the need for an OnClick event handler.

Figure 10 The objects


constituting the Get Info user
interface

1 unit GetInfoU;

2 interface

3 uses
4 Windows, Messages, SysUtils, Variants, Classes, Graphics,
5 Controls, Forms, Dialogs, StdCtrls, Buttons;

6 type
7 TfrmGetInfo = class(TForm)
8 { Standard RAD generated preamble; ref figures 9 & 10 }

Two Patterns Using Composition (3 Oct 2006) Chapter 10, Page 19


17 private
18 FUpdateValid: boolean;
19 procedure SetDefaultText;
20 public
21 property UpdateValid: boolean read FUpdateValid; // read only
22 end; // end TfrmGetInfo = class(TForm)

23 implementation

24 {$R *.dfm}

25 procedure TfrmGetInfo.btnUpdateClick(Sender: TObject);


26 begin
27 FUpdateValid := True; // writing, so must use private data field
28 Close;
29 end; // end procedure TfrmGetInfo.btnUpdateClick

30 procedure TfrmGetInfo.FormShow(Sender: TObject);


31 begin
32 SetDefaultText; // clear the text
33 edtString1.SetFocus;
34 FUpdateValid := False; // writing, so must use private data field
35 end; // end procedure TfrmGetInfo.FormShow

36 procedure TfrmGetInfo.FormClose(Sender: TObject;


37 var Action: TCloseAction);
38 begin
39 if not UpdateValid then // reading, so rather use the property
40 SetDefaultText; // clear the text
41 end; // end procedure TfrmGetInfo.FormClose

42 procedure TfrmGetInfo.SetDefaultText;
43 begin
44 edtString1.Text := ''; // set to required default string
45 edtString2.Text := ''; // set to required default string
46 end; // procedure TfrmGetInfo.SetDefaultText

47 end. // end unit GetInfoU

If this form is closed for any reason except a click on the Update button, the values of the
Edits are set to the required default through SetDefaultText.

Chapter 10, Page 20 Object orientation with Delphi (all rights reserved)
Ex 10.4 step 2 Using the input form

We’ll test this using the application’s main form to create a simple driver (figure 10).

Figure 11 Calling the input


Form

Figure 12 The constituents of


the main form

The program code is:

1 unit ShowInfoU;

2 interface

3 { Standard RAD generated preamble; ref figures 10 & 11 }

19 implementation

20 uses GetInfoU; // link to the other form

21 {$R *.dfm}

22 procedure TfrmShowInfo.btnGetInfoClick(Sender: TObject);


23 begin
24 frmGetInfo.lblString1.Caption := 'Name:';
25 frmGetInfo.lblString2.Caption := 'Phone No:';
26 frmGetInfo.ShowModal;
27 lblName.Caption := 'Name: ' + frmGetInfo.edtString1.Text;
28 lblPhone.Caption := 'Phone No: ' + frmGetInfo.edtString2.Text;
29 end; // end procedure TfrmShowInfo.btnGetInfoClick

30 end. // end unit ShowInfoU

This version overwrites the name and phone number with blanks if the user cancels or
closes frmGetInfo. If we preferred that these labels are altered only when the user clicks on
btnUpdate, we could insert a conditional statement and replace lines 27–28 by:

Two Patterns Using Composition (3 Oct 2006) Chapter 10, Page 21


if frmGetInfo.UpdateValid then
begin
lblName.Caption := 'Name: ' + frmGetInfo.edtString1.Text;
lblPhone.Caption := 'Phone No: ' + frmGetInfo.edtString2.Text;
end;

This is a simple program and it works. But if we get fussy, and in programming it often pays
to be fussy, there is a problem with each of the program lines we have entered (lines 24–28)
because the chaining breaks the ‘neighbours-only’ rule of the Law of Demeter. The problem
is the degree of coupling between frmShowInfo and frmGetInfo (from step 1). To keep the
user interface object frmShowInfo general, and so to encourage reuse by other forms, we set
the Captions explicitly. But this means that we have to know the names of the relevant
objects in frmGetInfo (lines 24–25) in order to set up the access chain. This breaks
frmGetInfo’s encapsulation. If in future we change the Labels to GroupBoxes, or even if we
just change the names of the Labels, each one of frmGetInfo’s users will have to change
accordingly. There are similar problems with lines 27–28 where we also explicitly address
objects contained within frmGetInfo. Thus frmShowInfo is coupled with five other objects:
frmGetInfo, lblString1, lblString2, edtString1 & edtString2.

Figure 13 Class relationships


with ‘typical’ approach

In line 26 we show the Form modally. This is important because if we call it non-modally
with Show rather than ShowModal, lines 27–28 will execute before the user has had time to
provide the input. This is a different way of breaking encapsulation: in frmShowInfo we
have to know that we must show frmGetInfo modally in order to use it.
There is a third problem too. Because frmGetInfo and its constituents are all
instantiations of Components, their properties and methods have Published visibility, which
is wider even than Public visibility. So frmShowInfo’s methods can change the size, colour,
position, Caption (etc, etc) of frmGetInfo and its constituent Components. Yet another,
serious, breakage of frmGetInfo’s encapsulation!
So what at first glance looks to be an innocent, everyday program turns out to be an OO
purist’s nightmare! (Of course, we’re over-dramatising the problems a bit here, but it serves
to illustrate the principles we are working with.)

Chapter 10, Page 22 Object orientation with Delphi (all rights reserved)
What can we do? Well, when trying to prevent encapsulation leakage, two thoughts
come up. First, can we use composition and then expose only what is needed? Second, can
we place an intervening class between frmShowInfo and frmGetInfo? We’ll combine both of
these in the Facade Pattern to reduce coupling and to increase encapsulation significantly.

Example 10.5 The Facade

Because of the problems we’ll recode example 10.4 and replace the explicit delegation with
an intermediate Facade object.

Ex 10.5 step 1 Implementing the Facade

In the new version, the main form (TShowInfo) will communicate solely with the Facade.
This in turn will delegate all the operations to the TGetInfo form (figure 12).

Figure 14 Introducing a Facade between the two user interface objects

Add another unit to the application:

1 unit FacadeU;

2 interface

3 uses
4 GetInfoU;

5 type

6 TMyFacade = class(TObject)
7 private
8 FIn2: string; // Keep these general
9 FIn1: string;
10 FInputForm: TfrmGetInfo;
11 FUpdateValid: boolean;
12 public
13 property In1: string read FIn1; // read only
14 property In2: string read FIn2; // read only
15 property UpdateValid: boolean read FUpdateValid; // read only

Two Patterns Using Composition (3 Oct 2006) Chapter 10, Page 23


16 procedure AcquireInput (A1stCaption, A2ndCaption: string);
17 constructor Create;
18 end; // end TMyFacade = class(TObject)

19 implementation

20 { TMyFacade }

21 constructor TMyFacade.Create;
22 begin
23 inherited;
24 FInputForm := frmGetInfo; // association: don’t create, just link
25 end; // end constructor TMyFacade.Create

26 procedure TMyFacade.AcquireInput (A1stCaption, A2ndCaption: string);


27 begin
28 FInputForm.lblString1.Caption := A1stCaption + ' :';
29 FInputForm.lblString2.Caption := A2ndCaption + ' :';
30 FInputForm.ShowModal;
31 FIn1 := FInputForm.edtString1.Text;
32 FIn2 := FInputForm.edtString2.Text;
33 FUpdateValid := FInputForm.UpdateValid;
34 end; // end procedure TMyFacade.AcquireInput

35 end. // end unit FacadeU

TMyFacade now has all the links that were formerly part of frmShowInfo (example 10.4,
step 2, lines 24–28) packaged as a single method call (lines 28–32). The client need now only
interact with TMyFacade. Thus the coupling with the client is reduced significantly and the
complexity of the interaction with the input form is concentrated in TMyFacade.
TMyFacade interacts with a RAD generated form, TfrmGetInfo, which is instantiated
automatically when the program starts. Here we are therefore not busy with a composition
relationship between TMyFacade and TfrmGetInfo, but with an association relationship.
Consequently TMyFacade does not need to propagate its construction or destruction to
TfrmGetInfo. TMyFacade does, however, still need a constructor to create the association
between the two.

Ex 10.5 step 2 Using TMyFacade

We must now change TShowInfo to use TMyFacade:

19 implementation

20 uses
21 FacadeU;

22 var
23 InData: TMyFacade;

Chapter 10, Page 24 Object orientation with Delphi (all rights reserved)
24 {$R *.dfm}

25 procedure TfrmShowInfo.btnGetInfoClick(Sender: TObject);


26 begin
27 // No explicit delegation or indirect coupling
28 InData.AcquireInput ('Name', 'Phone No'); // no chaining
29 lblName.Caption := 'Name: ' + InData.In1; // no chaining
30 lblPhone.Caption := 'Phone No: ' + InData.In2; // no chaining
31 end; // end procedure TfrmShowInfo.btnGetInfoClick

32 procedure TfrmShowInfo.FormShow(Sender: TObject);


33 begin
34 InData := TMyFacade.Create;
35 end; // end procedure TfrmShowInfo.FormShow

36 end. // end unit ShowInfoU

We change the uses clause to FacadeU (lines 20–21 above) and declare a TMyFacade
variable (line 23) which we create in the Form’s OnShow event handler (line 34). In the
btnGetInfo event handler we now interact with only one object, the instantiation of
TMyFacade (lines 28–30), rather than with five objects as previously (example 10.4, step 2,
lines 24–28). Access to the input form is now restricted to TMyFacade’s AcquireInput
method and to the two read-only properties In1 and In2.
If in the future our requirements change and we need to get the input from a database
query rather than through a user input form, we make all these changes to TMyFacade. Say,
for example, that instead of delegating the input collection to a Form, TMyFacade delegates
it to a database query, and that instead of using the AcquireInput method’s parameters for
Captions, it uses them to access database fields. These changes can all be accommodated
within TMyFacade while frmShowInput, and any other clients, remain blissfully unaware of
any changes. Without the Facade, clients such as frmShowInput would need significant
alteration to use the database. By allowing communication with only a single object (which
in turn delegates the operations as needed), a Facade reduces coupling between parts of a
system and so limits the impact of changes. Also, if any other form also needs the services of
the GetInfo form it too can work through this Facade and not delegate explicitly and
separately to each of the five objects as in example 10.4.
So, at the initial cost of some extra complexity, the improved encapsulation gives a much
more robust solution that is also more resilient in the face of future change and evolution.
Notice that the Facade exposes only those methods and data that the client needs. Here
the client sees only the AcquireInput method and the In1 and In2 properties and so can no
longer interfere with any of the other published properties of frmGetInfo (cf example 10.4,
step 2).
These benefits become far more obvious as overall system complexity rises and when the
Facade is used repeatedly and/or by several clients.

Two Patterns Using Composition (3 Oct 2006) Chapter 10, Page 25


Pattern 10.2 The Facade Pattern

At times, a program may need to interact in a relatively limited way with several related
objects or with a single very complex object. It would be preferable to provide a simple view
of these objects or this object and so reduce the potential coupling and/or simplify the
coding.

Therefore,

create a Facade, which is an intermediate class with the simplified interface and which
delegates operations implicitly to the set of objects or to the complex object. Because of the
delegation, clients need not engage with the full complexity of the encapsulated object(s)
and the Facade can accommodate any changes needed in the encapsulated object(s) without
involving the clients.
The Facade pattern reduces complexity in several ways. It provides a single access class
to a group of objects with all access by clients to the group through itself, so reducing
coupling. It enhances ing encapsulation by exposing only the required data and behaviour
and keeping all other data and behaviour hidden. It provides a simple interface to a
complex object in cases where the full complexity is not needed.

Chapter 10 summary

Main points:
1. Using delegation (in the form of composition or association) in implementing patterns.
2. Using the Adapter pattern to:
a. encapsulate and adapt legacy objects or software, and
b. to extend polymorphism to classes outside a particular hierarchy.
3. Object and class Adapters.
4. Using the Facade pattern to:
a. decrease system coupling and to increase cohesion, and
b. to simplify interfacing.
5. Fabricating an intermediate class between other classes.

Chapter 10, Page 26 Object orientation with Delphi (all rights reserved)
Objects as derived entities: Applying delegation; creating an adapter through delegation (an
object adapter) and through inheritance (a class adapter).
Objects as interacting entities: Adapting an existing object to work with a class that expects a
different object signature; reducing coupling by an intermediate object that presents a
facade to an existing object or objects.

Patterns: Adapter, Facade.

Pattern references

The Adapter and Facade patterns are both widely known and used, and are discussed by
Gamma et al (1995), Grand (1998), Lethbridge & Laganière (2001) and Shalloway & Trott
(2002), among others. Bennet et al (2002) apply the Facade pattern as part of a case study.

Bennet, S., McRobb, S. & Farmer, R. 2002. Object-oriented Systems Analysis and Design
using UML, 2nd ed, McGraw-Hill, Maidenhead.
Gamma, E., Helm, R., Johnson, R. and Vlissides, J. 1995. Design Patterns: Elements of reusable
object-oriented software. Addison-Wesley, Reading, MA.
Grand, M. 1998. Patterns in Java, vol 1. Wiley: New York.
Lethbridge, T. and Laganière, R. 2001. Object-oriented software engineering. McGraw-Hill:
Maidenhead.
Shalloway, A. and Trott, J. 2002. Design Patterns Explained: A new perspective on object-oriented
design. Addison-Wesley.

Problems

Problem 10.1 Study Chapter 10

Identify the appropriate example(s) or section(s) of the chapter to illustrate each comment
made in the summary at the end of chapter 10.

Two Patterns Using Composition (3 Oct 2006) Chapter 10, Page 27


Problem 10.2 A simple Adapter with a deep copy

Add Assign and AssignTo methods to the simple Adapter of example 10.1 step 2, and test
first Assign and then AssignTo with the driver program of example 10.1 step 5.

Problem 10.3 Comparison between the Adapter and


the Facade patterns

Some patterns seem to have the same, or very similar, implementations (eg UML diagrams
and code). However the patterns are used to match a particular problem in a particular
context and so the intentions of different patterns differ. As a novice OO programmer, one
tends to look at the implementation of the different patterns. However, as the
implementation aspects become more familiar, we start to see more clearly the match
between a particular problem and a particular pattern.
There are many similarities between the Adapter and the Facade. Discuss these and also
the differences between the two patterns, indicating when to use which pattern.

Problem 10.4 Facade for non-modal form display

Example 11.5 works because the GetInfo form is displayed modally. As an exercise, rewrite
this to show GetInfo non-modally and draw matching sequence diagram(s).
Some background may help in understanding some of the reason for this question. In
much of our programming we use synchronous interactions between objects. We see this in
typical procedure or function calls where the calling routine waits for the called routine to
complete before continuing further. It is also possible to have asynchronous interactions
between objects. In asynchronous interactions an object (ObjectA, say) initiates some
operation (in ObjectB, say) and then continues without waiting for the operation to
complete. If ObjectA depends on the outcome of this operation in ObjectB we need some
mechanism so that ObjectA can use the outcome of the operation once it is complete.
ShowModal creates a synchronous relationship between two interface objects while
Show creates an asynchronous relationship.

Chapter 10, Page 28 Object orientation with Delphi (all rights reserved)
Problem 10.5 Catalogue entry for the Adapter pattern

Continue building the pattern catalogue by creating a catalogue entry for the Adapter
pattern.

Problem 10.6 Catalogue entry for the Facade pattern

Create a catalogue entry for the Facade pattern.

Two Patterns Using Composition (3 Oct 2006) Chapter 10, Page 29

Potrebbero piacerti anche