Abstract Classes, Part 1 – Abstracting Behavior

About a month ago, I wrote about one of the pillars of object-oriented programming (specifically being Abstraction). In the post, I defined abstraction as the following:

Instead, we’re going to be abstracting ideas into their classes. And there’s a key idea here: A class should represent a noun.

And though that’s still true, the idea of abstract classes is something that’s different in object-oriented programming.

It sounds confusing, right? That is:

  • on one level, we have abstraction being defined as the idea that we take an idea and represent it in a class,
  • on another level, we have abstract classes which are used to help define functions that subclasses must implement.

And if that isn’t confusing enough, we mix this in with interfaces which provide a contract implementing classes must follow, and then we mix it with abstract classes which define methods that also must be implemented but can also implement methods of their own.

Confused yet? No worries. The whole point of the next three posts is to do the following:

  1. Define what abstract classes are,
  2. Describe the different in abstract classes and interfaces,
  3. Help decide when you want to use one over the other.

With that said, here’s the whole idea behind abstract classes.

Abstracting Behavior

First and foremost, there’s a difference in abstraction and abstract classes. The former refers to the idea of representing something in programming; the latter refers to an actual way to write code.

And one of the best ways I’ve found to what think about abstract classes in object-oriented programming is to think of them like this:

Abstract classes are a substitute for implementation.

Perhaps another way to think of them are as placeholders. Ultimately, they provide the behavior that the subclasses must implement.

How is this different than an interface? Remember that an interface defines a signature for a function (the function name, its arguments, and its visibility modifiers) that a class must implement.

Abstraction, on the other hand, provides a substitute for implementation that a subclass must implement. But perhaps this is best demonstrated through the use of code.

Abstraction in Practice

Let’s say that you’re working on a project and you find that you have functionality that exists in more than one place. Aside from violating the whole idea of DRY, it also has potential to be a place where you can abstract functionality into a base class and use it again.

Let’s consider this in the context of a publishing system. This isn’t necessarily how WordPress implements it, but it uses an idea with which we’re familiar: Taxonomies.

In WordPress, recall that we have Tags and Categories. There are subtle differences between the two (like if one if hierarchical or not), but they also share similar attributes like having a name and a slug.

A Taxonomy Abstraction

So we can start off by writing an abstract Taxonomy class that abstracts the common functionality into its own class.

In the code above, you’ll see that I’ve done the following:

  • declared the class abstract
  • defined several attributes that will be set in the constructor
  • provided several public functions with implementation,
  • added several protected methods.

The key take away from looking at this clas is that any class that implements this abstract class will automatically have the functionality defined in the constructor, the getName function, and the getSlug function.

They will not, however, have the implementation of the abstract functions. Those are what’s left up to be implemented by the subclasses (which I’ll share momentarily).

A Concrete Taxonomy: A Tag

Now that we have an abstract class defined, it’s possible to actually implement the abstraction. For example:

In the code above, notice that all the class does is provide the implementation for the abstract functions defined in the abstract class (which is specified by the extends function in the class definition).

Later in this article, I’ll share how to test this code but note that the above not only offers the functionality that you see but also the functionality of the Taxonomy class.

A Concrete Taxonomy: A Category

Before taking a look at this in action, I want to define a category, as well. This will include code that implements functions from the abstract class but also functions of its own.

See below:

Here, we have everything that comes with the Taxonomy class, but we’ve also defined our property for its parent ID and getter and setter methods. Though trivial in this case, it shows how categories, which are hierarchical, can function.

Further, if the category has no parent, then the ID is set to -1 which makes it easy to write for automated testing or even check to see if it has a parent.

Seeing It In Action

To demo this entire code, I’ve got a gist that includes all of the code in a single file. As a best practice, I don’t recommend this. Instead, each class should be kept in its own file, and each class should belong to a namespace.

But since this is purely for demonstration purposes, it suffices.

When you run this in the console, you should see something like the following output:

Abstract Classes: Behavior

You may need to add a few echo statements to make sure that it’s creating new lines, but that’s up to you.

What About Interfaces?

So, at this point:

  • we have a working definition of what abstract classes are,
  • we have an example of what abstract classes look like,
  • and we have a working demo of how they can perform.

Next, I’ll take a deeper dive into discussing the differences between abstract classes and interfaces, when you may want to use one over the other, or when you may want to use them in conjunction with one another.