So let’s talk about coupling! Coupling is a complicated problem because you can never get rid of it completely. All that you can do is control how much it spreads. And there’s more than one way to do that.
One of these ways is the dependency inversion principle. It’s one of the most important principles in object-oriented design. And it’s why it’s part of the famous SOLID design principles. (It’s the “D” in SOLID.)
The dependency inversion principle isn’t something that you see with WordPress. That’s because most WordPress developers don’t use object-oriented programming. (But it’s always a good time to start if you’re not using it!) And the dependency inversion principle only helps when you’ve been using it for a while.
But what makes the dependency inversion principle special? Why is it such almost a mandatory aspect of object-oriented design? These are good questions that you deserve an answer to.
What is the dependency inversion principle?
Robert C. Martin (also known as Uncle Bob) created the dependency inversion principle. He’s a famous software engineer who’s contributed a lot to the field of object-oriented programming. For example, all the principles in SOLID are his. (He’s famous for this and his contribution to the Agile Manifesto.)
As we said in the introduction, the goal of the dependency inversion principle is to reduce coupling in your code. The principle works by reversing (that’s why it’s called inversion!) the dependency relationship between your classes. It states that:
A. High-level modules should not depend on low-level modules. Both should depend on abstractions.
B. Abstractions should not depend on details. Details should depend on abstractions.
This is a pretty intimidating set of statements. What do we mean by module, abstraction and details!? These aren’t words that you’re used to seeing with object-oriented programming.
It’s ok if this doesn’t quite make sense yet. (That’s why you’re reading this article!) We’re going to go through each of these statements to see what they mean. But first, let’s talk about the typical way that you use classes together.
A conventional object-oriented plugin
To better understand the dependency inversion principle, you have to look at how you use classes together without it. Let’s imagine that we have a small object-oriented plugin. Here are some of the classes that we created for it:
class MyPlugin_Plugin { /** * MyPlugin admin page * * @var MyPlugin_AdminPage */ private $admin_page; /** * Constructor. */ public function __construct() { $this->admin_page = new MyPlugin_AdminPage(); } // ... } class MyPlugin_AdminPage { /** * MyPlugin options * * @var MyPlugin_Options */ private $options; /** * Constructor. */ public function __construct() { $this->options = new MyPlugin_Options(); } // ... } class MyPlugin_Options { /** * MyPlugin options * * @var array */ private $options; /** * Constructor. */ public function __construct() { $this->options = array(); } // ... }
We have three classes in the code sample above. We have the MyPlugin_Plugin
class with a constructor. This constructor creates an instance of the MyPlugin_AdminPage
class.
Then we have the MyPlugin_AdminPage
class. It also has a constructor like the MyPlugin_Plugin
class. But the difference is that it creates an instance of the MyPlugin_Options
class.
The last class is the MyPlugin_Options
class. It’s also the simplest. It only uses an internal options
array. Like our two other classes, we initialize the array in the constructor.
Dependencies between our classes
Now, let’s look at how these three classes depend on each other. This isn’t too hard to deduce based on our earlier description of the code. Using this information, let’s create a diagram describing the dependencies between our classes:
As you can see, the MyPlugin_Plugin
class depends on the MyPlugin_AdminPage
class. And the MyPlugin_AdminPage
class depends on the MyPlugin_Options
class. Meanwhile, the MyPlugin_Options
class has no dependencies.
But we also ordered these classes from highest to lowest. The MyPlugin_Plugin
class is the highest while the MyPlugin_Options
class is the lowest. This lets us explain one of the two statements in the dependency inversion principle.
High-level modules should not depend on low-level modules. Both should depend on abstractions.
It’s worth clarifying now that, for us, the term “module” really means “class”. This means that, according to the dependency inversion principle, our three classes have a problem. They shouldn’t depend on each other like they do right now. Instead, they should depend on abstractions.
Depending on interfaces
But what does abstraction mean in the context of the dependency inversion principle? It often just means an interface. Instead of depending on each other, our three classes should depend on interfaces.
This is where the idea of inversion comes from. It’s not about reversing the order of the coupling between our classes. We’re not looking to have the MyPlugin_Options
class depend on the MyPlugin_AdminPage
class and so on. (That’d be weird!)
No, it’s about depending on an interface instead of a concrete class. The inversion is between class and the interfaces that it implements. We’re coupling ourselves to one of these interfaces instead of the class itself.
That’s the fundamental idea behind the dependency inversion principle. You want your classes to depend on interfaces and not other classes. This isn’t possible in our example because it doesn’t use interfaces.
Adding interfaces
So let’s rectify this issue and create interfaces for the classes in our example. We’re going to do this from the bottom up. That means starting with the class with no dependencies and moving up from there.
interface MyPlugin_OptionsInterface { /** * Gets the option for the given name. Returns the default value if * the value does not exist. * * @param string $name * @param mixed $default * * @return mixed */ public function get($name, $default = null); /** * Sets an option. * * @param string $name * @param mixed $value */ public function set($name, $value); }
So here’s our MyPlugin_OptionsInterface
interface. It’s a small interface with two methods: get
and set
. These two methods let us interact with our plugin options.
Now, we need to update our MyPlugin_Options
class. We want it to implement the MyPlugin_OptionsInterface
interface that we just created. You can find the updated class below:
class MyPlugin_Options implements MyPlugin_OptionsInterface { /** * MyPlugin options * * @var array */ private $options; /** * Constructor. */ public function __construct() { $this->options = array(); } /** * {@inheritdoc} */ public function get($name, $default = null) { return isset($this->options[$name]) ? $this->options[$name] : $default; } /** * {@inheritdoc} */ public function set($name, $value) { $this->options[$name] = $value; } // ... }
As you can see, the MyPlugin_Options
class hasn’t changed too much. We only added the two methods defined in the MyPlugin_OptionsInterface
interface. The get
method uses a ternary operator to return the value in the options
array if it’s set. Meanwhile, the set
method inserts the value
parameter in the options
array using the name
parameter as the key.
Reworking our constructors
Now, that we have the MyPlugin_OptionsInterface
interface for the MyPlugin_Options
class. We can update our MyPlugin_AdminPage
class to use the interface instead of the class. This is going to require that we rework the MyPlugin_AdminPage
constructor:
class MyPlugin_AdminPage { /** * MyPlugin options * * @var MyPlugin_OptionsInterface */ private $options; /** * Constructor. */ public function __construct(MyPlugin_OptionsInterface $options) { $this->options = $options; } // ... }
When using an interface, we can’t create a MyPlugin_Options
object in the constructor like we did earlier. We need to pass the object implementing the MyPlugin_OptionsInterface
interface as an argument. We then assign the object to the internal options
variable.
We don’t want to care about the details
So why is this better? Why couldn’t we just pass a MyPlugin_Options
object as an argument? Why is the interface better? The answer lies in the second statement of the dependency inversion principle.
Abstractions should not depend on details. Details should depend on abstractions.
So, as we know from earlier, abstractions are our interfaces. But what does it mean by details? It means what’s going on inside our classes.
The statement is a warning meant to prevent us from creating another type of dependency. This time it would be between our classes and interfaces. We shouldn’t design an interface based on the needs of our class like we just did with the MyPlugin_OptionsInterface
interface.
Instead, it’s the interface that should dictate what the class should do. And then it’s up to the class to figure out how to do it. The implementation “details” shouldn’t matter to the interface.
That’s why the contract analogy is so good with interfaces. When a class implements an interface, we don’t care how it does things. All that we care about is that it satisfies the interface’s contract.
Creating the proper abstractions
Because of this second statement, creating an interface isn’t a simple task. We can’t just create a generic interface that mirrors the methods found in our class and call it a day. (If only it were that easy!) Such an interface won’t reduce coupling in any meaningful way.
A better way to reduce coupling is to design our interfaces based on the needs of the class that’ll use it. But didn’t we just say that we shouldn’t do that? Not quite! There’s a subtle difference.
Designing an interface for admin pages
The best way to highlight this difference is by using an example. Let’s design an interface for our MyPlugin_AdminPage
class. But this time, we’ll use the needs of the MyPlugin_Plugin
class to do it.
The needs of our plugin class
So what does the MyPlugin_Plugin
class need from the MyPlugin_AdminPage
class? Well, it could want a way to register an admin page with WordPress. So we could have something like this:
class MyPlugin_Plugin { /** * MyPlugin admin page * * @var MyPlugin_AdminPageInterface */ private $admin_page; /** * Constructor. */ public function __construct(MyPlugin_AdminPageInterface $admin_page) { $this->admin_page = $admin_page; } /** * Loads the plugin into WordPress. */ public function load() { $this->admin_page->register(); } // ... } $myplugin = new MyPlugin_Plugin($admin_page); add_action('wp_loaded', array($myplugin, 'load'));
We made two important changes to the MyPlugin_Plugin
class. First, we modified its constructor. It now wants an object implementing the MyPlugin_AdminPageInterface
interface as an argument. Above that MyPlugin_AdminPageInterface
object is represented by the admin_page
variable. We still assign that object to the admin_page
internal variable.
Next, we added a new method called load
. It’s a method that we hooked to the wp_loaded
hook. We picked this hook because it’s the last hook of the WordPress loading process.
Meanwhile, the load
method itself only does one thing. It calls a register
method of the object stored in the admin_page
internal variable. This register
method is what the MyPlugin_Plugin
class needs from the MyPlugin_AdminPageInterface
interface.
An interface to meet these needs
Using this knowledge, we can design the MyPlugin_AdminPageInterface
interface. The goal for it is to solve the problem that we highlighted in the MyPlugin_Plugin
class. Here’s what it would look like based on what we know:
interface MyPlugin_AdminPageInterface { /** * Register the admin page with WordPress when WordPress loads. */ public function register(); }
We know that the interface needs a method called register
. Its job is to register the WordPress admin page. We also specify in the method description that this happens when WordPress loads.
That’s because we can’t have any code in an interface. So we need to be thoughtful documenting our method. We need enough information so that the class can implement the “details” in the correct way.
Implementing the details
Now that we have our abstraction, we can look at implementing the details in the MyPlugin_AdminPage
class. We know from the interface that we need a register
method. So let’s add it:
class MyPlugin_AdminPage implements MyPlugin_AdminPageInterface { // ... /** * Register the admin page with WordPress when WordPress loads. */ public function register() { } // ... }
Let’s leave the method empty for now so that we can think about the implementation details. First, we need to add the admin page. We do that using the add_menu_page
function.
class MyPlugin_AdminPage implements MyPlugin_AdminPageInterface { // ... /** * Display plugin admin page. */ public function display_page() { // ... } /** * Register the admin page with WordPress when WordPress loads. */ public function register() { add_menu_page('My Plugin Admin Page', 'My Plugin Admin Page', 'edit_posts', 'myplugin_admin_page', array($this, 'display_page')); } // ... }
Adding the call to add_menu_page
function like we did above works if you’re in the WordPress admin. Whenever you navigate to your plugin’s admin page, WordPress will call the display_page
method. That said, theregister
method will generate an error when you’re not in the WordPress admin. So we need to add an extra layer to control when we call the add_admin_menu
function.
class MyPlugin_AdminPage implements MyPlugin_AdminPageInterface { // ... /** * Add admin page to WordPress admin menu. */ public function add_page() { add_menu_page('My Plugin Admin Page', 'My Plugin Admin Page', 'edit_posts', 'myplugin_admin_page', array($this, 'display_page')); } /** * Display plugin admin page. */ public function display_page() { // ... } /** * Register the admin page with WordPress when WordPress loads. */ public function register() { add_action('admin_menu', array($this, 'add_page')); } // ... }
So here’s the extra layer needed to add our menu page without any errors. We added a new method called add_page
where we put our call to add_menu_page
. We then hooked the method to the admin_menu
hook which is the recommended hook to use in the codex.
On abstractions and details
This problem with the add_menu_page
function is important. We could redesign the MyPlugin_Plugin
class with this problem in mind. (But that’s a decision that you have to make.) For example, we could call the register
method from the admin_menu
hook.
That said, we didn’t design our MyPlugin_Plugin
class that way. And the class implementing the MyPlugin_AdminPageInterface
interface needs to deal with that reality. That’s what we mean when we say that abstractions shouldn’t depend on details.
This diagram above is another way to look at this relationship between abstractions and details. If the MyPlugin_Plugin
class is part of a layer, the MyPlugin_AdminPageInterface
interface would be part of that same layer. We design both at the same time.
Meanwhile, the MyPlugin_AdminPage
class lies on a layer below the MyPlugin_Plugin
class. It implements the MyPlugin_AdminPageInterface
interface from the above layer. But, by being on different layers, we don’t have to design the two at the same time.
This is also why it’s a bad idea to create a generic interface based on a class. From a layer perspective, the MyPlugin_AdminPage
class shouldn’t dictate the inner workings of the MyPlugin_Plugin
class. It needs to be the other way around.
Always use an abstraction
Now, if we look back to our MyPlugin_Options
class and its interface, it doesn’t quite fit the layers model that we have so far. It would feel weird from the layer containing the MyPlugin_AdminPage
class to also contain the interface for the MyPlugin_Options
class. In practice, you’ll use the MyPlugin_Options
class in a lot of different layers.
Why do we need an interface for the MyPlugin_Options
class then? The reason for it is that a class should never depend on a concrete class. It should always depend on abstractions.
That’s because an interface always creates less coupling than a concrete class. This is true even for a generic interface. That’s why it’s ok to create a generic interface like the MyPlugin_OptionsInterface
interface here.
The big picture
So here we are! We’ve reworked our plugin classes using the dependency inversion principle. The result is classes that are less coupled to one another.
And here’s our final class dependency diagram. It illustrates how our modified classes, as well as our new interfaces, depend on one another. It’s not as simple as our original one so let’s walk through it.
We still have three layers like we had in our original diagram. That said, the flow of dependencies changed. It doesn’t all go from top to bottom anymore.
The MyPlugin_Plugin
class depends on the MyPlugin_AdminPageInterface
interface. Both of the MyPlugin_Plugin
class and the MyPlugin_AdminPageInterface
interface are on the same layer. This is the direct result of our use of dependency inversion. We reduced the coupling in the MyPlugin_Plugin
class by not making it depend on another layer.
That said, the dependency inversion principle doesn’t work all the time. The MyPlugin_AdminPage
class still depends on the MyPlugin_OptionsInterface
interface which is on the layer below it. But we still reduced the coupling by switching our dependency to the MyPlugin_OptionsInterface
interface.
It’s a question of scale
So that’s the dependency inversion principle! It might look like a lot of work for almost no benefit. (There’s a good chance you might feel that the code is way more complicated as a result.) But that’s just because our example was so small.
With object-oriented programming, you can have dozens, hundreds or even thousands (gasp!) of classes. Coupling is a real issue when working at that scale. And that’s also where the dependency inversion principle comes into its own.
But you shouldn’t wait until your code reaches that scale to start practicing it. In fact, it’s better to get used to it as early as possible. This will allow you to design classes that won’t always break as your code grows.