July 30, 2014
Hot Topics:
RSS RSS feed Download our iPhone app

PHP Pattern Principles

  • October 13, 2005
  • By Matt Zandstra
  • Send Email »
  • More Articles »

Loosening Your Coupling

To handle database code flexibly, we should decouple the application logic from the specifics of the database platform it uses. Fortunately, this is as easy as using a PEAR package: PEAR::DB.

Here is some code that uses PEAR::DB to work first with MySQL, and then with SQLite:

require_once("DB.php");
$dsn_array[] = "mysql://bob:bobs_pass@localhost/bobs_db";
$dsn_array[] = "sqlite://./bobs_db.db";
foreach ( $dsn_array as $dsn ) {
print "$dsnnn";
$db = DB::connect($dsn);
$query_result = $db->query( "SELECT * FROM bobs_table" );
while ( $row = $query_result->fetchRow( DB_FETCHMODE_ARRAY ) ) {
printf( "| %-4s| %-4s| %-25s|", $row[0], $row[2], $row[1] );
print "n";
}
print "n";
$query_result->free();
$db->disconnect();
}

Note that we have stripped this example of error handling for the sake of brevity.

The DB class provides a static method called connect() that accepts a Data Source Name (DSN) string. According to the makeup of this string, it returns a particular implementation of a class called DB_Common. So for the string “mysql://”, the connect() method returns a DB_mysql object, and for a string that starts with “sqlite://”, it returns a DB_sqlite object. You can see the class structure in Figure 5.



Click here for a larger image.

Figure 5: PEAR::DB decouples client code from database objects

The PEAR::DB package, then, enables you to decouple your application code from the specifics of your database platform. As long as you use uncontroversial SQL, you should be able to run a single system with MySQL, SQLite, MSSQL, and many others without changing a line of code (apart from the DSN, of course, which is the single point at which the database context must be configured).

This design bears some resemblance to the Abstract Factory pattern described in the Gang of Four book, and later in this book. Although it is simpler in nature, it has the same motivation, to generate an object that implements an abstract interface without requiring the client to instantiate the object directly.

Of course, by decoupling your system from the specifics of a database platform, the DB package still leaves you with your own work to do. If your (now database-agnostic) SQL code is sprinkled throughout your project, you may find that a single change in one aspect of your project causes a cascade of changes elsewhere. An alteration in the database schema would be the most common example here, where an additional field in a table might necessitate changes to many duplicated database queries. You should consider extracting this code and placing it in a single package, thereby decoupling your application logic from the details of a relational database.

Code to an Interface Not an Implementation

This principle is one of the all-pervading themes of this book. We saw in the last section that we can hide different implementations behind the common interface defined in a super class. Client code can then require an object of the super class’s type rather than that of an implementing class, unconcerned by the specific implementation it is actually getting.

Parallel conditional statements, like the ones we built into Lesson::cost() and lesson::chargeType(), are a common signal that polymorphism is needed. They make code hard to maintain because a change in one conditional expression necessitates a change in its twins. Conditional statements are occasionally said to implement a “simulated inheritance.”

By placing the cost algorithms in separate classes that implement CostStrategy, we remove duplication. We also make it much easier should we need to add new cost strategies in the future.

From the perspective of client code, it is often a good idea to require abstract or general types in your methods’ parameter lists. By requiring more specific types, you could limit the flexibility of your code at runtime.

Having said that, of course, the level of generality you choose in your argument hints is a matter of judgment. Make your choice too general, and your method may become less safe. If you require the specific functionality of a subtype, then accepting a differently equipped sibling into a method could be risky.

Still, make your choice of argument hint too restricted, and you lose the benefits of polymorphism. Take a look at this altered extract from the Lesson class:

function __construct( $duration,
FixedPriceStrategy $strategy ) {
$this->duration = $duration;
$this->costStrategy = $strategy;
}

There are two issues arising from the design decision in this example. Firstly, the Lesson object is now tied to a specific cost strategy, which closes down our ability to compose dynamic components. Secondly, the explicit reference to the FixedPriceStrategy class forces us to maintain that particular implementation.

By requiring a common interface, you can combine a Lesson object with any CostStrategy implementation.

function __construct( $duration, CostStrategy $strategy ) {
$this->duration = $duration;
$this->costStrategy = $strategy;
}

You have, in other words, decoupled your Lesson class from the specifics of cost calculation. All that matters is the interface and the guarantee that the provided object will honor it.

Of course, coding to an interface can often simply defer the question of how to instantiate your objects. When we say that a Lesson object can be combined with any CostStrategy interface at runtime, we beg the question, “But where does the CostStrategy object come from?”

When you create an abstract super class, there is always the issue as to how its children should be instantiated. Which one do you choose in which condition? This subject forms a category of its own in the GoF pattern catalog. I cover more of these in my book PHP 5 Objects, Patterns, and Practice published by Apress.

The Concept That Varies

It’s easy to interpret a design decision once it has been made, but how do you decide where to start?

The Gang of Four recommend that you “encapsulate the concept that varies.” In terms of our lesson example, the “varying concept” is the cost algorithm. Not only is the cost calculation one of two possible strategies in the example, but it is obviously a candidate for expansion: special offers, overseas student rates, introductory discounts, all sorts of possibilities present themselves.

We quickly established that subclassing for this variation was inappropriate and we resorted to a conditional statement. By bringing our variation into the same class, we underlined its suitability for encapsulation.

The Gang of Four recommend that you actively seek varying elements in your classes and assess their suitability for encapsulation in a new type. Each alternative in a suspect conditional may be extracted to form a class extending a common abstract parent. This new type can then be used by the class or classes from which it was extracted. This has the effect of

  • Focusing responsibility
  • Promoting flexibility through composition
  • Making inheritance hierarchies more compact and focused
  • Reducing duplication

So how do we spot variation? One sign is the misuse of inheritance. This might include inheritance deployed according to multiple forces at one time (lecture/seminar, fixed/timed cost). It might also include subclassing on an algorithm where the algorithm is incidental to the core responsibility of the type. The other sign of variation suitable for encapsulation is, of course, a conditional expression.





Page 3 of 4



Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Sitemap | Contact Us

Rocket Fuel