Abstract classes and interfaces – part 1

As we’ve already seen, PHP has introduced a lot of new features to support object-oriented programming. The one we’re looking at now deals with a special concept known as abstraction.

Now, abstraction in OO terms is similar to what it means in regular language. We talk about something being abstract when we deal with general patterns or themes, but no concrete details. And just like in life, PHP coders use abstraction to identify fundamental strategy and overarching design without getting bogged down in the nitty-gritty implementations.

When it comes to classes and objects, there are two key tools to explicitly support abstraction provided by the PHP 5 language. Interfaces and abstract classes.

We’ll start by taking a look at an abstract class. A class is abstract if it contains abstract methods. And a class can contain abstract methods by either explicitly declaring them or by extending a class with abstract methods and then not defining them.

The keyword you use on the class is, simply enough ‘abstract’. You use the same keyword on the methods. Here’s a simple abstract class that defines one abstract method:

{show example code}

Notice that the abstract method declaration looks sort of like a regular method declaration, except it contains the keywork ‘abstract’ in the front and ends with a semi-colon instead of braces. That’s because it only defines a method’s name and arguments, and not any of its implementation.

The idea here is that you might have many different child classes that extend an abstract class and implement its abstract methods in various ways. Without knowing the specific implementation, your other objects can interact with those child classes.

Now, the important thing to understand is that you cannot get a new instance of an abstract class. PHP will only create objects of non-abstract classes. If you want the functionality that an abstract class provides you must extend it into a non-abstract class that defines all its abstract methods.

For instance, if I define an abstract class called ‘Report’ with an abstract method called ‘display’, my other scripts know that regardless of which type of report I might have, it will always be able to display itself. I might define a Report_HTML child class for displaying a report inside an HTML table. I might define another one called Report_CSV for outputting a version of a report that can be saved in a CSV file. And I might define another one called Report_XLS for saving a report to an Excel Spreadsheet.

The point is that there are going to be many different scripts involved in getting data to the report or working with the report, and most of them don’t need to know how the report is going to be displayed in order to do their work correctly. So, rather than creating completely separate classes for each situation, we create one report class that has just one abstract method that needs to be defined separately in its child classes. This lets us use the same code in as many locations as possible, and code re-use leads to easier maintenance and management.

Since I like to point out design patterns from time to time, this is a good time to mention one called ‘Decorator’. The Decorator pattern is probably the one most often used by programmers who don’t even realize they’re implementing an actual named pattern. All it is is using child classes to add more specific functionality to parent classes. Extending an abstract class is one obvious way to accomplish that.

The other abstraction tool is similar. While abstract classes are essentially regular classes that happen to have some abstract methods, interfaces are sort of like classes that only have abstract methods. Interfaces don’t define properties, though they can define constants. And while a class can only extend one parent class, abstract or otherwise, it may implement any number of interfaces.

Here’s how we define an interface:

{show example code}

Notice that it really does look a lot like a class definition. You don’t need the ‘abstract’ keyword on the method declarations, but they do resemble abstract methods nonetheless. And since the basic idea is that an interface defines a way for other objects to interact with them, interfaces may only define public methods.

To tell the PHP engine that a particular class is going to implement the methods defined in an interface, we use the keyword ‘implements’ in our class definition. And if a class implements multiple interfaces, we just separate them with commas. However, no class can implement two interfaces that both define the same method name.

Interfaces can extend interfaces, and in fact, they can extend multiple interfaces. To extend an interface, you just use the extends keyword, and to extend more than one interface you just separate them with commas.

{show example code}

One thing to note about interfaces is that since there’s no actual code, it is vitally important that you comment them fully. The method declarations on an interface tell the programmer the method’s name and the arguments it should accept, if any. But if one of those methods should return something, the only way to let the programmer know that is in the comments. Here’s an example of a well-commented interface:

{show example code}

Notice that each method’s comments clearly explain what the method should do, in broad terms. With this documentation, it is feasible to begin writing code that will use objects that implement a certain interface even before the implementing classes have been defined.

Objects of classes that extend abstract classes or implement interfaces are considered to be instances of those abstract classes or interfaces, the same as they’re considered instances of the classes that define them. This makes it possible to use type-hinting to specify that your functions and methods require specific interfaces or classes as arguments.

To understand how powerful type-hinting is in connection with abstraction, imagine you have an interface called Readable. Among other things, it defines a method called ‘readLine’ and another method called ‘hasNext’. ‘readLine’ is responsible for returning a string from some content source, and advancing an internal pointer, so that a subsequent call to ‘readLine’ will get the string that comes right after. The ‘hasNext’ method returns true if there is are any lines left to get, and false if there aren’t. Knowing just those two methods, I can write a function like:

{show example code}

My ‘writeFile’ function knows that it’s getting a Readable as its first argument, and will write it out to whatever filename it’s told to. It doesn’t matter if the particular Readable is based on another file, a stream, the contents of a database, or anything else. And I can trust that so long as my various Readable implementations correctly return the data they ought to, my writeFile function will work with any of them. This sort of versatility means I can create functions such as writeFile before I’ve even created the actual object types that will be passed into them.

So that’s abstraction in a nutshell. You create abstract methods with lots of documentation to explain what they’ll do, and then you write classes that define those methods in a concrete, useful way.

January 30 2010 03:25 pm | Schmategories

Comments are closed.