Tutorials

Design Patterns - Introduction

Views: 35498
Rating: 5/5
Votes: 3

Note

Originally, this tutorial was huge, covering both Introduction and pattern descriptions. Instead I am now posting the Introduction, the pattern descriptions will be posted individually.

Introduction

Implementing Design Patterns is gradually getting more common in the PHP world. The hype around Ruby on Rails, which is based on the Model-View-Controller architectural pattern, has spawned a generation of PHP based frameworks which embrace this pattern also, paving the way for others to embrace design patterns in general in their PHP applications.

Much about Design Patterns is about interpretation (which is why it is often so heavily discussed), so please do not take everything I write for granted: deduct your own conclusions, get your own interpretation.

1 What are design patterns?

1.1 Where do patterns come from?

Originating from architectural design (as in design of buildings), when design patterns crossed over to computer programming in the 1980’s, only a small group of people using a language called “SmallTalk” were applying them. In 1995, a group of four authors released a book called “Design Patterns: Elements of Reusable Object-Oriented Software”.

The four authors where nicked the “Gang of Four”. SmallTalk, but also C++ where the languages applying design patterns at that time. It is still the most respected book on design patterns to this date. “Gang of Four” (abbr. GoF) is also commonly used to refer to the book, rather than the authors.

Soon after, 'Gurus' such as Martin Fowler started publishing their own works, perhaps most notably “Patterns of Enterprise Application Architecture” (abbr. PoEAA). By then, most books where using Java in their examples. The Java community has played a big part in the evolution of Design Patterns, not in the last place thanks to efforts from Alur et al. with their “J2EE Core Patterns”.

1.2 What are they?

But what exactly are these “Design Patterns” I keep rambling on about?

If you’re an experienced coder, you’ve more than likely run into problems that you solve in a, for you, standard way. Design patterns are more or less the description of that: a problem and a solution (or more of them), in the context of a system.

The GoF describes patterns to be made up of four parts, not mentioning “context”, and adding “pattern name” and “consequences”. However, they also provide this description of the patterns in their book:

“The design patterns in this book are descriptions of communicating objects and classes that are customized to solve a general design problem in a particular context.”

Design patterns catalogs do not just provide problems and their solution, but also provides named handles for them. This makes it possible to communicate with your fellow developers referencing patterns without having to lay it out.

But pattern descriptions often encompass multiple solutions to the same problem. So even if you are talking about the same problem, you may be talking about a different solution, with different implications.

2 GoF Principles

The GoF book has been adopted as the defining work of modern software development. Consequently, the principles they presented in it have been widely adopted also.

These are:

  • Code to an Interface
  • Favour Composition over Inheritance
  • Encapsulate the Concept that Varies

The following sections will cover these principles.

2.1 Code to an Interface

Named by the GoF as "Program to an interface, not an implementation”.

Partly, coding to an interface comes naturally when properly applying polymorphism. You read about this in OO PHP Part 2. For implementations to be used interchangeably, the client code should be ignorant of the underlying implementation; you code to an interface. Coding to an interface is a way to achieve decoupling (see part 2), modularity and extensibility.

It seems almost like tradition to illustrate this by example of a database abstraction layer. It’s a good example, so why break with tradition?

In above example the client code, once a connection has been established, doesn’t care what specific database (implementation of DB) it is using. Because all implementations share the same interface, a call like the following could be used no matter what RDBMS is used. The more specific to certain implementations your interface becomes, the tighter the coupling.

$result = $db->select(array('field1' => 'alias1'), array('criteria1'));

This promotes extensibility, for example you could add a fourth implementation say DB_Oracle without having to modify any of the client code . It also promotes modularity. The DB package in this example could also be used in other projects.

2.2 Favour Composition over Inheritance

The previous example is a classic situation in which inheritance is the means of promoting extensibility. However in many cases, extending classes at compile time like that can cause unnecessary, repeating logic. Not to mention when relations become more complex, inheritance can cause tight coupling. To favour composition improves flexibility of relations between entities. In order to “encapsulate the concept that varies”, inheritance alone is insufficient.

GoF on this subject:

“But class inheritance has some disadvantages, too. First, you can't change the implementations inherited from parent classes at run-time, because inheritance is defined at compile-time. Second, and generally worse, parent classes often define at least part of their subclasses' physical representation. Because inheritance exposes a subclass to details of its parent's implementation, it's often said that "inheritance breaks encapsulation".”

Inheritance breaks encapsulation. This isn't something you can fully negate with defensive programming (see part 2). Even if all your properties are declared private, subclasses are still imposed a structure by its parent. You can put behaviour you think is common to all implementations in a superclass, only to find later that implementation C needs some slightly different behaviour than A and B.

Abstracting out behaviour, and placing it in a composite or aggregate object, is far more flexible than abstracting it into a superclass.

2.3 Encapsulate the Concept that Varies

As I said before, these three “principles” are very closely related. If you’re still a bit lost about the text in the previous section, this is where it will all start to make sense. I hope.

Say I have a base “Config” class, which provides an interface to reading and writing configuration options for my application.

Take a look at the following diagram:

The child classes both have a different purpose: Module is responsible for handling configuration of modules in my application, FileHandler for configuration of handlers for images, Javascript files and so on.

But what if I want to able to use two different storage methods? I could pass the storage method passed as an argument, and have some condition switching in each class. That would cause repeating logic; extensibility takes a beating. Try to imagine what would happen if you wanted to add a third storage method.

So, we’re going to attempt to separate the storage method from the subject of the configuration.

Continuing with only inheritance, this is how that might look:

Start to see something wrong?

Let’s pretend all is well, and imagine how the code in these subclasses would look like. There would still be a lot of repeating logic. The classes would be performing very similar tasks: storing, retrieving, updating of data. Extensibility takes a beating, still.

Adding a third storage method would create three subclasses per subject, all performing similar tasks. Just imagine if you had a third or fourth concept to add, besides the data storage and subject. That would require an impossible number of classes, but more importantly, turn into maintenance hell.

This is where encapsulation through composition comes in.

Config has a StorageHandler object, eliminating the need to create subclasses for each subject depending on the storage method used. I’ve encapsulated the concept of data storage. By doing so, I’ve caused decoupling of the concepts of configuration and data storage: the storage handler implementations are no longer exposed to the needs of Config.

Both Xml_Handler and Ini_Handler are now independent components which can be reused in other parts of your application.

Important note:

If you have read part 3 (UML, Classes and Relations) of this series, you might be a little confused by the above diagram. The GoF speak of “composition” (composite aggregation), yet the example shows regular aggregation. Unfortunately, the GoF do not make this distinction: composition could mean both types of aggregation.

3 Delegation

Delegation is a simple concept, but it's a term you should be familiar with. Delegation is a natural result of favouring aggregation over inheritance. The term refers to ‘delegating’ a request to a related object (or class). A quick example:

public function makeCake()

{

        return $this->baker->bakeCake($this->_cakeType);

}

When a client requests makeCake(), it could care less how it's made. The receiver of the request apparently isn't responsible for the baking of the cake itself, so it delegates the request to an aggregate object, baker, to handle the request. It sneaks in a little meta data as well.

You will find many Design Patterns that use this concept.

4 In conclusion

This tutorial was a basic Introduction into the world of design patterns, you will learn some actual patterns in the coming series of mini tutorials. Stay tuned.