Reality seems valueless by comparison with the dreams of fevered imaginations; reality is therefore abandoned.
The earliest phase of social formations found in historical as well as in contemporary social structures is this: a relatively small circle firmly closed against neighboring, strange, or in some way antagonistic circles.
Neither the life of an individual nor the history of a society can be understood without understanding both.
In their book, Design Patterns: Elements of Reusable Object-Oriented Software, Gamma, Helm, Johnson, and Vlissides (Addison-Wesley) employ a UML that may have slight variations from 1990s era UML standards, as well as contemporary UML 2.0 standards. However, those variations are slight, and by employing the GoF version of a UML, the reader will be able to compare the design patterns using PHP in this book with the Gang of Four’s original work. So whatever arguments may exist about the best UML for programming, you will find strict consistency between the UML in this book and the original work by Gamma and his associates.
If you are not familiar with UMLs, you will need patience. As you work with design patterns, they will become clearer in the context of the design and specific usage with PHP. Because of the minimum data typing used in PHP, implementing a design directly from a UML is very difficult until you are able to make the adjustments for data type and understanding the pattern itself.
Previous chapters displayed some simple class diagrams, and this section shows more details of class diagrams and how to use them with design patterns. In general, design pattern class diagrams display relations and communication between participants (classes and interfaces). For example, Figure 4-1 shows a typical class diagram of a design pattern. In this particular diagram, you will find five participants:
Client (implied)
Creator
ConcreteCreator
Product
ConcreteProduct
Each of the participants is a class or interface (abstract class or
interface). The names of interfaces are in italics whereas concrete
classes are in bold roman text. Note that the Client
is
shown in a light gray box. That means that it is implied in the pattern,
and in the case of the Factory Method pattern, the Client
class is not part of the pattern.
The Client
class is often an integral part of design patterns; sometimes it
is implied, and sometimes it is absent from the diagram altogether.
Clients make requests from the main program, and the arrows help to show
the different relations between the Client
and the main
part of the program. Often, programmers use the name “main” to indicate
the client, but with design patterns, the “main” terminology is
misleading. The main part of the program is in the interrelated
participants and not the client.
Figure 4-2 is the same pattern minus the pseudocode annotations to provide a clearer focus on the fundamental elements in the pattern.
The Factory Method pattern instantiates objects through a factory
(Creator
) to separate the instantiation
process from the requestor. The client wants (requests) a product of some
sort, but instead of instantiating it directly from the ConcreteProduct
, it is directed (as seen in the
class diagram) to make the request through the Creator
. Without class diagrams, knowing where
to direct the client to make a request would be ambiguous, as would all
participant relations. Class diagrams provide a view of the design pattern
so that you can quickly see the relationships illustrated in the UML. The
exact nature of relationships is specific, and the following sections help
break down the symbols used for the pattern participants and the
connections.
The Gang of Four refer to the classes and interfaces that make up a design pattern as participants. The name of the participant is in boldface. Interfaces (interfaces and abstract classes) are italicized. Some participants are shown with a line beneath the name with a list of key methods. Abstract methods are italicized, and concrete methods are in normal type face. Figure 4-3 shows a close look at the Creator interface.
In concrete implementations of interfaces, the abstract methods are concrete as well. Figure 4-4 shows the implementation of the Creator interface.
Note that the method, called AnOperation()
in the interface, is not included
in the concrete class. That’s because it has been implemented already and
inherited by the ConcreteCreator
.
However, the FactoryMethod()
must be
implemented because it was an abstract method in the parent class.
A final notation associated with participants is the pseudocode annotations. Figure 4-5 shows a clearer view of the annotations removed from the Factory Method pattern.
Pseudocode annotations provide further information about the structure of the design pattern. They are designed to give the developer a better sense of different code expectations with participants.
Pseudocode annotations provide further information about the structure of the design pattern. They are designed to give the developer a better sense of different code expectations with participants (see Figure 4-6).
The associations and their meanings may vary in different renditions of the UML and this collage is a somewhat simplified UML of the relations that can be found in Chapter 1 of Design Patterns: Elements of Reusable Object-Oriented Software, where you can find more details if desired. In case you wish to use additional design patterns with PHP not covered in this book, you will be familiar with the UML used by GoF.
The basic and possibly the most common relationship
between participants in a design pattern is the
acquaintance. An acquaintance relationship is where
one participant holds a reference to another participant. It is
represented by a simple arrow between the Client
and ISubject
and the Proxy
and RealSubject
, as shown in a Proxy
design pattern in Figure 4-7.
In order to see a concrete example of such an association, the
following listing of the Proxy
class
shows what an association would look like in PHP:
<?php class Proxy extends ISubject { private $realSubject; protected function request() { $this->realSubject=new RealSubject(); $this->realSubject->request(); } public function login($un,$pw) { //Evaluates password etc. if($un==="Professional" && $pw==="acp") { $this->request(); } else { print "Cry 'Havoc,' and let slip the dogs of war!"; } } } ?>
Generally, when one class holds a reference to another class, it would merely need to have a declaration. In a strongly typed language like C# where declarations include the data type, the “association” reference would look like the following:
private RealSubject realSubject;
As you can see in the Proxy listing, there’s also a declaration for a private variable with the same name:
private $realSubject;
The problem is that the variable can be instantiated as any data type. So, in order to hold a reference, the variable either has to be extended from a concrete instantiation of the variable, or it must be instantiated in the class where it is declared. In this case, it is instantiated:
$this->realSubject=new RealSubject();
So now, the Proxy
class holds a
reference to the RealSubject
class
and has an acquaintance relation with it.
The Gang of Four point out that no single code set can demonstrate what an aggregate relationship looks like, so keep in mind that the example used here simply shows what an implemented design pattern (Strategy) using aggregation looks like. In some respects, an aggregate relation is like an acquaintance relation but stronger. As Gamma et al. note, aggregation implies that an aggregate object and its owner have identical lifetimes. That is something like heart and lungs. As long as the heart keeps pumping blood, the lungs can move oxygenated blood throughout the system. Each is independent as an operating organ, but if one stops, so will the other.
The Strategy design pattern includes an aggregate relationship between
the Context
class and the Strategy
interface, as shown in Figure 4-8.
Now, look at a PHP listing of a Context
class:
<?php class Context { private $strategy; public function __construct(IStrategy $strategy) { $this->strategy = $strategy; } public function algorithm($elements) { $this->strategy->algorithm($elements); } } ?>
The code hint reference is to the IStrategy
interface. (The interface name is
IStrategy
of the Strategy participant
in the design pattern.) In this way, you can see that the Context
class holds a reference to the
Strategy interface through code hinting and without having to
instantiate an implementation of IStrategy
in the Context
participant (class). The Client
class will have to provide a concrete
strategy implementation when instantiating an instance of the Context
class. For example, a client making a
request would include a ConcreteStrategyA
:
<?php interface IStrategy { public function algorithm($elements); } ?>
As can be seen in the simple IStrategy
interface, it has a single method,
algorithm()
. However, the Context
class appears to have implemented the
algorithm()
method. Actually, the
Context
class is configured with a
concrete implementation of IStrategy
through the constructor function. The important feature of the
relationship is to note how the two objects form an
aggregation and how they have identical
lifetimes.
Of all the relations, the aggregate relation is the most difficult to explain and understand because of its many variations. Rather than attempt further explanation at this time and risk oversimplifying and misrepresenting it, as you go through the design patterns in the book that use this aggregation, its specific use will be explained.
The Gang of Four uses the same notation for inheritance and implementation: a triangle. Figure 4-9 shows an abstract class and interface with child classes.
Single inheritance or implementation uses a single triangle (triangle-on-a-stick), whereas indications of multiple inheritance or implementation display the triangle lying flat on the common connecting lines to the concrete classes.
You may wonder why GoF chose to use the same triangle symbol for both inheritance and implementation. In part, it is because virtually all design patterns that use inheritance from a class do so from an abstract class. Abstract classes imply implementation of the abstract methods, so whether interfaces or abstract classes are employed, implementation is part of the relationship. In many places in their book, GoF use abstract classes and interfaces interchangeably (referring to them simply as interfaces) because both include interfaces used by the child classes. Likewise, in many design patterns, it does not matter whether abstract classes or interfaces are used in the pattern. In one implementation of the design pattern, the programmer will use an abstract class, and in another implementation of the same design pattern, interfaces are used.
When one object creates an instance of another in a design
pattern, the notation is a dashed line with an arrow pointing to the
object that has been instantiated. You will see this kind of
relationship in patterns that use factories such as the Factory Method
(see Chapter 5) or Abstract
Factory design patterns, but you will encounter it in some other
patterns as well. Figure 4-10 shows a class
diagram of the Factory Method with the ConcreteCreator
using a factoryMethod()
to create instances of
ConProductA
and ConProductB
. The Client
holds references to both the Creator
and Product
interfaces so that it can specify the
exact product request.
In the discussion of acquaintance relationships between classes using PHP, in some cases it may be necessary to instantiate a class to hold reference to another class or interface. Such instantiation is due to creating a data type to hold the reference and not for using the instantiated object. Where this is necessary in PHP, do not use the dashed line. The relation is acquaintance, and even though an instance is created, it is only for establishing the acquaintance.
Sometimes you will see a class diagram where the arrows in acquaintance or aggregation relations have a ball at the end of the arrows, as in Figure 4-11.
The black ball means that multiple objects are being referenced or aggregated. The Flyweight pattern is used to reference multiple instances of shared objects or different objects.
Unlike a class diagram that shows the classes, object
diagrams show only the instances and arrows indicating the object
referenced. The naming convention employed is a class instance name headed
by the lowercase letter a. So, an instance of a class
named Client
would be aClient
. The text in the lower half of the
object diagram modules is the name of the target object or a
method.
A good example of using an object diagram can be found in the Chain of Responsibility design pattern. The pattern is used where more than a single object may handle a request, such as a help desk application. The client issues a request and then a handler interface passes on the request until a concrete handler is found to deal with the request. Figure 4-12 illustrates the chain.
In Figure 4-12, the request is passed from A to B to C and so on until the request can be handled and terminates to search for a handler. Figure 4-13 shows the same process in an object diagram.
Object diagrams provide another way of viewing a specific implementation of a design pattern and the relations between the objects generated through the pattern. They can help clarify object relations.
The final diagram GoF uses is one to trace the order in which requests are executed. These interaction diagrams are like object diagrams in the naming of objects, but their timeline is vertical—from top to bottom. Solid lines with arrowheads point to the direction of the request, and dashed lines with arrowheads indicate instantiation. Figure 4-14 shows the same Chain of Responsibility sequence as Figure 4-13, but in an interaction diagram. (The gray time direction arrow and time labels have been added but do not appear in the actual diagrams.)
The vertical rectangles indicate the time at which the object is active. As can be seen in Figure 4-14, the client object is active the entire time, the first concrete handler becomes active with the request from the client, and the second concrete handler does not become active until the request is passed from the first concrete handler.
Before going on to examine the first design pattern and implementing it in PHP, I want to pause a moment and look at what we’re trying to do with diagrams and notations. Just in case we forget, diagrams and the notations associated with them are explaining, examining, and building design patterns. The extent to which they are useful in assisting us, we can employ them. However, the second they are not useful, they should be abandoned and new ones created.
For years, the only programming diagram I knew was the old flowchart. It looks fine, and while I rarely used them for program planning or as guides for writing a program, I understood their use. However, in looking at them now, I realize they might be very bad tools for object-oriented programming. Flowcharts are guides to sequential programming. In contrast, the UMLs developed for OOP look and feel different. They break up a program into modules called classes, and notations show relations and interactions.
If you look, you can probably find tools that will help draw UMLs, but I’d recommend against it. In Unified Modeling Language User Guide, 2nd Edition, when discussing automated UML tools, Grady Booch points out the following:
Given automated support for any notation, one of the things that tools can do is help bad designers create ghastly designs much more quickly than they ever could without them.
If you treat the UMLs as aids for thinking, you’ll probably be better off as far as your code designs are concerned and understand design patterns a lot better. For example, Figure 4-15 shows the kind of diagram (Strategy design pattern) I’ve used with PHP projects to good effect.
As I start working on the project, I’ll make more sketches with more details, annotations, comments, and bits of code. I am forced to think while I sketch out the pattern and its details. With larger projects and groups of programmers, some kind of UML drawing tools may be handy for communication between programmers for the sake of clarity. At this time, though, pencil and paper stand as my main UML tools (I like to use the discarded pages from my cartoon-a-day calendar).
As far as UMLs go, rather than thinking of “standards,” think of how they can be usefully employed. Booch suggests a diagram taxonomy divided into Structure and Behavior Diagrams, with Interaction Diagrams being a major subtype of the behavior category. Of these, I have found statecharts to be especially useful for thinking about designs using state machines—the State design pattern in particular. The statecharts help the developer focus on states, change of states, triggers, and transitions. So while statecharts are not part of the UML set GoF uses, they can be handy for thinking through problems that involve state machines like those in State design patterns. (See Chapter 10, The State Design Pattern, for examples of statecharts.)
For getting started with design patterns, besides becoming familiar with the notations, learn about what the notations mean. For example, knowing the meaning of aggregation is more important than knowing it is represented by a line with a diamond on its tail and an arrowhead on its head. Further, if you start using the design pattern UML, you can better frame programming problems as having design pattern solutions. By using a pencil and a piece of paper, your thought process will be better connected than if you use automated tools, and you’ll have a better chance of understanding design patterns.
Get Learning PHP Design Patterns now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.