And here’s another one ;).
One of the reasons that procedural programmers are hard to convince that object-oriented programming is so great, is that the examples given aren’t very spectacular: most of them can be rewritten easily in procedural code. Also, much of the so-called OOP code out there isn’t object-oriented at all. The PEAR library for example, is in fact not a real object-oriented library, even though the developers claim it is. (Don’t worry, I’ll explain my reasons for saying this below ;).)
The biggest advantage of OOP is re-use. Re-use comes in three flavors (mostly), but only one is used a lot, while the second and third are almost never used. PEAR, for example, uses only two flavors (and the second flavor minimally).
So what are these flavors of re-use? Well, here they are:
1. Object instantiation
This is the easiest and most popular form of re-use in OOP. phpPete gave a good example with his menusystem, so I won’t give one here. You write a blueprint for some type, and you can reuse it over and over again simply by instantiating it (creating a variable and calling methods on it). In procedural programming you can achieve the same result by writing a module: a set of functions operating on the same datastructure. There is no advantage whatsoever in using OOP instead of PP for this type of code, except that OO-addicts (like me) say that using a class instead of a module makes the code easier to read (Example: ‘$database->query($sql)’ instead of ‘query($database, $sql)’).
2. Class inheritance
The second form of re-use in OOP is class inheritance: given some class, you ‘extend’ it, adding new features or overriding existing ones. Inheritance comes in many forms, and I’ll briefly describe three of the forms here, comparing each of them to procedular programming.
An interface (or: abstract base class, or even: virtual base class) is a class that defines a number of methods, but doesn’t implement them. A subclass of the interface class must implement all methods to be a valid interface implementation. For example:
class Database
{
// create a database object
function Database($host, $dbname) { }
// connect to the database
function connect($username, $password) { }
// disconnect from the database
function disconnect() { }
// execute a query and return the result
function query($sql) { }
// are we connected?
function isConnected()
}
Now, subclasses of the Database interface must implement all methods to be a valid interface implementation. If we were to write Database classes for MySQL, PostgreSQL, Oracle or whatever, they must all conform to the interface above. Why would we want that? Because it allows us to create code that executes methods on any Database-object. Or maybe you need a plug-in system, allowing developers to add code to the software at a later time.
The advantage of this kind of inheritance (and re-use) shows when using a strongly typed language (Java/C++). If I were to implement a subclass of class Database, the interpreter (or compiler) makes sure I do not forget to implement some method, that all methods have the right number of arguments, all of the right type, and that every method returns a result of the right type. There is no such thing in procedural programming, and the result is that programmers have to write code according to some long and dull manual. With OOP, you can throw away the manual, and trust your compiler to do the dirty work for you.
Because PHP doesn’t explictly check types, writing interfaces isn’t as useful as it is in Java or C++. That isn’t to say it has no use at all. You can still write an abstract class, and use that as the guidelines you must follow if you want to implement the interface (instead of the boring manual). Also, you can put a ‘die(“Method not implemented!”);’ in every method in the interface, so that when you call that method on an object with an incomplete implementation, the program halts immediately.
Say you have some procedural module, but it doesn’t quite do what you want it to do. If the module consists of many functions, you might be able to replace a couple so that it does. However, if the old module must be maintained at the same time, this means that you have to write a new module with all the same functions as the old one. In the new module, every function simply calls the equivalent function in the old one, except the ones you want to redefine. The result is that you end up with large blocks of code that do virtually nothing.
With OOP, you can achieve the same result by simply writing a subclass, and replacing only the methods you want to replace, keeping all other methods intact. The beauty of this is that the subclass is easier to write (less to type, so less chance of bugs) and easier to understand: only the code that adds something new must be written.
The PEAR library implements this kind of re-use a lot. Take a look at classes DB_mysql, DB_pgsql and so on. Class DB_common is the superclass of both classes, and defines basic methods. The two subclasses override only the necessary methods to get their work done. (But one of the biggest problems of PEAR is that almost every method of class DB_common has to be overridden to get it working. That’s a clear sign of bad design!)
Most procedural programmers are familiar with callback functions: you write some function that takes as an argument (a pointer to) a function, and calls that function with yet another set of arguments. Those who ever did that in C will hopefully agree with me that it’s pretty hard to do, because the code looks frightening (unless you’re a C-guru), and it’s easy to make mistakes.
As an example, take a function that implements ‘bubble sort’ on arrays with value of any type. To be able to sort the values, the function needs to call a user-specified function that compares two values. If the first value is bigger than the second, the function returns true, meaning that the values should be swapped:
function bubbleSort(&$array, $compare)
{
for ($i = 0; $i < count($array) - 1; $i++)
{
for ($j = 1; $j < count($array); $j++)
{
if ($compare($array[$i], $array[$j]))
{
$temp = $array[$i];
$array[$i] = $array[$j];
$array[$j] = $temp;
}
}
}
}
function compareIntegers($i, $j)
{
return $i > $j;
}
$array = array(8, 2, 4, 3, 1, 9, 7)
bubbleSort($array, "compareIntegers");
By writing a new comparison function, the sort-algorithm can easily be reused. This example is pretty simple, but imagine what happens if some algorithm needs many callback functions! With OOP, we can achieve the same goal in a much cleaner way (with ‘hooks’):
class BubbleSort
{
function BubbleSort(&$array)
{
// Same algorithm as before, except that the line:
// if ($compare($array[$i], $array[$j]))
// is replaced by:
// if ($this->compare($array[$i], $array[$j]))
}
function compare($i, $j)
{
return $i > $j
}
}
$array = array(8, 2, 4, 3, 1, 9, 7)
$bs = new BubbleSort($array);
If I have to sort an array of ‘Blob’-elements, all I need to do is override class BubbleSort and re-implement the compare method:
class BlobBubbleSort
{
function compare($blob1, $blob2)
{
return $blob1->getValue() > $blob2->getValue();
}
}
$blobArray = array(new Blob("foo"), new Blob("bar"), new Blob("this"), new Blob("that"));
$bbs = new BlobBubbleSort($blobArray);
Because this is such a small example, you might not see at once why this is ‘better’ than the equivalent procedural code with callback functions. But consider a complex system that needs many callback functions; in that case using classes leads to code that is much more readable and understandable. A classic example is a windowing toolkit. I won’t go into the gory details here, so if you want to know more, take a look at one of the many windowing toolkits on the Internet like Swing (Java) or Qt (C++). Typically, those toolkits contain many classes with empty methods, that might or might not be implemented by subclasses. At other places in the toolkit calls are made to those methods, so as soon as you override an empty method and define its behavior, the framework immediately changes its behavior as well. As you can hopefully see by now, hooks provide a very powerful means to extend software easily.
Sadly, I haven’t found any occurrences of hooks in PEAR. Draw your own conclusions from that…
There are more ways to use inheritance, but let’s keep it at the three mentioned. I’ll now continue with the third flavor of re-use in object-oriented programming:
3. Object composition
This is maybe the most powerful and beautiful application of OOP, but as it’s also the most difficult one, almost nobody uses it. The idea behind object composition is that you implement classes that don’t do a single job completely by themselves, but require additional classes to make them complete. That makes it easy to mix and match object of various types to get completely different behavior from the same classes. As you probably expected by now, PEAR does nothing of the sort.
As a simple example, consider again the bubblesort algorithm given earlier. The BubbleSort class looks a bit weird, because:
- I have to instantiate an object just to run the algorithm. However, the class has no member variables, so why would I need an object?
- When I override class BubbleSort (as in BlobBubbleSort), there is a potential danger that the algorithm itself can be overridden as well. This is not a clear separation between the main algorithm (bubble sort) and the specialized behavior (comparing two objects)
To solve this, I implement the algorithm in two classes, instead of one:
class BubbleSort
{
// This is a static method
function sort(&$array, $compareObject)
{
// Same algorithm as before, but now we do
// $compareObject->compare($i, $j);
}
}
class IntCompare
{
function compare($i, $j)
{
return $i > $j;
}
}
$array = array(8, 2, 4, 3, 1, 9, 7)
BubbleSort::sort($array, new IntCompare);
class BlobCompare
{
function compare($blob1, $blob2)
{
return $blob1->getValue() > $blob2->getValue();
}
}
$blobArray = array(new Blob("foo"), new Blob("bar"), new Blob("this"), new Blob("that"));
BubbleSort::new BubbleSort($blobArray, new BlobCompare);
Again, this example is very simple, but hopefully you’ll see I’m now using object composition instead of just inheritance: by supplying the ‘sort’-method with a different object, the algorithm automatically behaves differently as well. Also note that this example looks a bit like the procedural-style callback function, which more or less shows that callbacks are very important indeed. Whether to use hooks or object composition depends on the problem at hand, but it can be a difficult decision to make.
Object composition is something you just can’t do without, as it allows you to abstract from the problem your working on, and lets you write code in layers. It’s almost a requirement for a proper object-oriented programming library to support object composition. (Did I mention already that PEAR doesn’t have this? Ah, never mind…)
Final remarks
The best object-oriented programs aren’t the ones that use only one or two of the techniques I mentioned here, but the ones that combine all of them. For example, consider I write an interface ‘Object’, that defines a method ‘getValue’. I use this class as a baseclass for all classes in the system to store all sorts of values. By writing one single class ‘ObjectCompare’ and implementing the simple method ‘compare’ in that class, I can instantly sort arrays of any kind of object! Thus, by combining inheritance and object composition, I can do a whole lot with very little code. Achieving this same result with procedural code not only requires more code, but also results in code that’s difficult to understand.
To end this long (and boring) post, consider a program I just wrote for one of the web sites I’m working on. The program reads a text file containing a menu system, and prints it in nicely formatted HTML. The file it reads is the following:
category | name | link
PHP | SitePoint | http://www.sitepoint.com
PHP | PHP.net | http://www.php.net
Search | Google | http://www.google.com
Search | HotBot | http://hotbot.lycos.com
What I want to do is the following:
- Each line in the file must be accessible as an array. The first line of the file contains the key. Thus if the first record would be in the variable $record, the value $record[‘name’] would be ‘SitePoint’.
- The complete file must be stored in memory, and each record must be accessible as described above.
- The records in the file must be traversed.
- Each record must be printed in HTML
- The records must be ordered on category
To be more specific, I want to map the above text file to the following HTML:
<h1>PHP</h1>
<p>
<a href="http://www.sitepoint.com">SitePoint</a>
<br>
<a href="http://www.php.net">PHP.net</a>
</p>
<h1>Search</h1>
<p>
<a href="http://www.google.com">Google</a>
<br>
<a href="http://hotbot.lycos.com">HotBot</a>
</p>
To do that, I need to write code. So I start hacking away, and end up with only one (1!) statement:
Loop::run(
new DataFileIterator(new DataFile('menu.dat', new DataFileReader)),
new MenuPrinter
);
Let me try and explain what it does, from the outside to the inside:
- Class Loop is a simple class that uses object composition. Its sole method ‘run’ requires an ‘iterator’ and a ‘loop manipulator’ as its arguments. The method implements a simple iteration, that is influenced by the manipulator.
- Class DataFileIterator implements the ‘Iterator’-interface for DataFiles. There are also iterators for trees, query results, built-in arrays, strings, you name it. They can all use the Loop class.
- Class DataFile stores file-based tables in memory. It allows access to each record in the file. However, it doesn’t know how to parse lines in files itself, so it has to be passed a DataFileReader class that does that specific job.
- Class MenuPrinter is a ‘loop manipulator’ I wrote earlier that generates the required HTML. Although I haven’t included the code for that class here, as it would require even more explanation of the classes I use, please believe me when I say that it is an extremely simple class.
What if the text files have a different format? Well, then I replace the DataFileReader. What if I need to print the menu in a different layout? Well, then I replace the MenuPrinter. What if I stop using text files, and want to use a real database instead? Well, then I simply execute a query on a database and use a QueryIterator to process the results, like this:
// $database is a Database connection object
Loop::run(
new QueryIterator(
$database->query('select category, name, link from menu, order by category')
),
new MenuPrinter
);
As you see, I don’t have to re-implement a single thing. All I do is combine existing objects in a different manner, to get the behavior I want. And that, my dear ladies and gentlemen, is the true power of object-oriented programming!
Vincent