September 2, 2014
Hot Topics:
RSS RSS feed Download our iPhone app

PHP Pattern Principles

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

Using Composition

We can use the Strategy pattern to compose our way out of trouble. Strategy is used to move a set of algorithms into a separate type. In moving cost calculations, we can simplify the Lesson type. You can see this in Figure 4.

Figure 4: Moving algorithms into a separate type

We create an abstract class, CostStrategy, which defines the abstract methods cost() and chargeType(). The cost() method requires an instance of Lesson, which it will use to generate cost data. We provide two implementations for CostStrategy. Lesson objects work only with the CostStrategy type, not a specific implementation, so we can add new cost algorithms at any time by subclassing CostStrategy. This would require no changes at all to any Lesson classes.

Here’s a simplified version of the new Lesson class illustrated in Figure 4:

abstract class Lesson {
private $duration;
private $costStrategy;
function __construct( $duration, CostStrategy $strategy ) {
$this->duration = $duration;
$this->costStrategy = $strategy;
}
function cost() {
return $this->costStrategy->cost( $this );
}
function chargeType() {
return $this->costStrategy->chargeType( );
}
function getDuration() {
return $this->duration;
}
// more lesson methods...
}

The Lesson class requires a CostStrategy object, which it stores as a property. The Lesson::cost() method simply invokes CostStrategy::cost(). Equally, Lesson::chargeType() invokes CostStrategy::chargeType(). This explicit invocation of another object’s method in order to fulfill a request is known as delegation. In our example, the CostStrategy object is the delegate of Lesson. The Lesson class washes its hands of responsibility for cost calculations and passes on the task to a CostStrategy implementation. Here it is caught in the act of delegation:

function cost() {
return $this->costStrategy->cost( $this );
}

Here is the CostStrategy class, together with its implementing children:

abstract class CostStrategy {
abstract function cost( Lesson $lesson );
abstract function chargeType();
}
class TimedCostStrategy extends CostStrategy {
function cost( Lesson $lesson ) {
return ( $lesson->getDuration() * 5 );
}
function chargeType() {
return "hourly rate";
}
}
class FixedCostStrategy extends CostStrategy {
function cost( Lesson $lesson ) {
return 30;
}
function chargeType() {
return "fixed rate";
}
}

We can change the way that any Lesson object calculates cost by passing it a different CostStrategy object at runtime. This approach then makes for highly flexible code. Rather than building functionality into our code structures statically, we can combine and recombine objects dynamically.

$lessons[] = new Seminar( 4, new TimedCostStrategy() );
$lessons[] = new Lecture( 4, new FixedCostStrategy() );
foreach ( $lessons as $lesson ) {
print "lesson charge {$lesson->cost()}. ";
print "Charge type: {$lesson->chargeType()}n";
}
// output:
// lesson charge 20. Charge type: hourly rate
// lesson charge 30. Charge type: fixed rate

As you can see, one effect of this structure is that we have focused the responsibilities of our classes. CostStrategy objects are responsible solely for calculating cost, and Lesson objects manage lesson data.

So, composition can make your code more flexible because objects can be combined to handle tasks dynamically in many more ways than you can anticipate in an inheritance hierarchy alone. There can be a penalty with regard to readability, though. Because composition tends to result in more types, with relationships that aren’t fixed with the same predictability as they are in inheritance relationships, it can be slightly harder to digest the relationships in a system.

Decoupling

It makes sense to build independent components. A system with highly interdependent classes can be hard to maintain. A change in one location can require a cascade of related changes across the system.

The Problem

Reusability is one of the key objectives of object-oriented design, and tight-coupling is its enemy. We diagnose tight coupling when we see that a change to one component of a system necessitates many changes elsewhere. We aspire to create independent components so that we can make changes in safety.

We saw an example of tight coupling in Figure 2. Because the costing logic was mirrored across the Lecture and Seminar types, a change to TimedPriceLecture would necessitate a parallel change to the same logic in TimedPriceSeminar. By updating one class and not the other, we would break our system, but without any warning from the PHP engine. Our first solution, using a conditional statement, produced a similar dependency between the cost() and chargeType() methods.

By applying the Strategy pattern, we distilled our costing algorithms into the CostStrategy type, locating them behind a common interface, and implementing each only once.

Coupling of another sort can occur when many classes in a system are embedded explicitly into a platform or environment. Let’s say that you are building a system that works with a MySQL database, for example. You might use functions such as mysql_connect() and mysql_query() to speak to the database server.

Should you be required to deploy the system on a server that does not support MySQL, you could convert your entire project to use SQLite. You would be forced to make changes throughout your code, though, and face the prospect of maintaining two parallel versions of your application.

The problem here is not the dependency of the system upon an external platform. Such a dependency is inevitable. We need to work with code that speaks to a database. The problem comes when such code is scattered throughout a project. Talking to databases is not the primary responsibility of most classes in a system, so the best strategy is to extract such code, and group it together behind a common interface. In this way you promote the independence of your classes. At the same time, by concentrating your “gateway” code in one place, you make it much easier to switch to a new platform without disturbing your wider system.





Page 2 of 4



Comment and Contribute

 


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

 

 


Sitemap | Contact Us

Rocket Fuel