SOLID OO Principles

June 3, 2011

Uncategorized

As much as we're like to believe that "loose coupling, high cohesion" is enough, when you actually dive into the concept, you find that it's more descriptive than prescriptive. If you want to know how to actually apply these to your day to day development, you have to get into the SOLID principles which describes the five tangible aspects that "good" OO design should contain.

First, is the Single Responsibility Principle or SRP. In short, it means that like things should be together and nothing more. If you have a class that handles all aspects of one process or everything about a single object, that's good. If your class also manipulates other objects or is just a place to catch a bunch of methods, you're doing it wrong. Within the web2project world, we have a Link class with these methods:

class CLink extends w2p_Core_BaseObject {
    public function __construct() { }
    public function loadFull(CAppUI $AppUI, $link_id) { }
    public function check() { }
    public function delete(CAppUI $AppUI) { }
    public function store(CAppUI $AppUI) { }
    public function hook_search() { }
} 

At first glance, you can tell that most of these methods have to do with normal CRUD operations and probably interact directly upon the CLink object. Unfortunately, we have one other method on that class:

public function getProjectTaskLinksByCategory(CAppUI $AppUI, $project_id = 0, 
    $task_id = 0, $category_id = 0, $search = '') { }

Yes, unfortunately that is a real method name..

When you glance at this method, the name alone should be a sign that a lot of happening here. In fact, since there are potentially three other object id's as input, it's safe to assume we're interacting with those three other objects. If those objects ever change, we end up with "shotgun surgery" where a small change to one part of the system causes little changes all over the place. This is painful to maintain even in the short term.

Next, we have the Open/Closed Principle or OCP or in more plain terms "software entities should be open for extension but closed for modification." You might have heard this said another way in your favorite Open Source project as "Don't Hack Core." In practice, this one can be enforced by extremely selective use of the private and final keywords.

If you are considering reasons to mark something as private, stop and reevaluate your goals. Are you just trying to limit its use outside the class or is there really only One True Way to perform the task? If you want to limit its outside use, consider the protected keyword instead. It allows for your class to be extended and for the method to be overridden if needed.

Next, we have the Liskov Substitution Principle or LSP and honestly, this is the hardest one for me to grasp. In short, the idea is that objects in a given class hierarchy should be replaceable with their subtypes without modifying the "correctness" of the system. In proper terms, it means that method/class preconditions cannot be strengthened, post conditions can't be weakened, all exceptions thrown must equally interchangeable, and method signatures should be completely compatible.

I'll hold my example for this one until Monday. It's quite long and involved.. and still doesn't make 100% sense to me. Input will be welcome.

The next concept is the Interface Segregation Principle or ISP. The formal definition is "many client specific interfaces are better than one general purpose interface" but in layman's terms, it's much simpler. As time goes on and requirements change, our systems grow. Without careful consideration and strategy, our class hierarchy will both grow and get "messy". Methods that made sense on one class at the beginning of the project or didn't have a logical location end up in odd places. Suddenly, we end up with a painful game of Go Fish.

"Excuse me mister Project class, can you tell me who is assigned to work on you? Nope, go fish."

The most useful aspect of this principle is that – once you being to apply it – the oddities stand out. For example, when we look at this (contrived) example:

interface w2p_Interfaces_Authenticator {
    public function authenticate($username, $password);
    public function getUserId();
    public function userExists();
    public function changePassword();
}

All of those methods relate to user validation and/or authentication, so they make sense together. Any object that implements the interface should be "highly coherent," but then look at this (luckily, contrived) example:

interface w2p_Interfaces_Modules_Setup {
    public function install();
    public function upgrade($old_version);
    public function configure();
    public function uninstall();
    public function changeDisplayOrder();
}

The first four methods make sense on a "Setup" interface and describe what looks like a reasonable lifecycle of a module. The last method – changeDisplayOrder – has little to do with setup and seems to relate to a View layer consideration. Something is wrong here.. or in other terms, we have a "code smell".

The final concept of SOLID is the Dependency Inversion Principle or DIP. In the computer science world, the formal definition is "Depend upon abstractions, not concretions." In the real world, it means when we have to introduce class dependencies, we should be as broad as possible in what we expect. Within web2project, part of our class structure looks like this:

abstract class w2p_Core_BaseObject {
    public function __construct($table, $key, $module = '') {}
    public function load($oid = null, $strip = true) {}
    public function store($updateNulls = false) {}
    public function check() { }
}
class CProject extends w2p_Core_BaseObject {
    public function __construct() { }
    // load is inherited
    public function store(CAppUI $AppUI) { }
    public function check() { }
}
class CLink extends w2p_Core_BaseObject {
    public function __construct() { }
    // load is inherited
    public function store(CAppUI $AppUI) { }
    public function check() { }
}

Since we have uniform interfaces, instead of our controllers being wholly dependent on the CProject or CLink object, we can interact with the w2p_Core_BaseObject to do things like this:

class w2p_Controllers_Base
{
    public function __construct(w2p_Core_BaseObject $object, $delete,
             $prefix, $successPath, $errorPath) { }
}

The controller can interact with each of these objects in exactly the same ways without knowing exactly which object its using. That is powerful and makes the controller reusable all over the system.

As a side note, if you look closely at CProject and CLink with load, store, and check, you'll notice that this looks a lot like Nouns, Verbs, and Uniform Interfaces.. sound familiar?

Regardless, the SOLID principles alone aren't going to give you better OO Design. There are legitimate reasons and situations to bend or even break them but it should not be on a whim or from ignorance. On the flip side, we still have to apply our own judgement on where and how to apply them too. We need to consider what we gain and/or improve as a result. Checking off the "we support SOLID" box isn't sufficient.

About Keith Casey

This should be something about myself. I've had suggestions that it should be "useful information" but I'm not sure about that.

View all posts by Keith Casey

4 Responses to “SOLID OO Principles”

  1. tonymarston Says:

    @ reestyle
    I still don’t see how that work work in PHP (or any other language for that matter). If I have an abstract DB class which contains dozens of possible methods, how is it possible to define a subclass which only has access to a subset of those methods? AFAIK it is not possible as any subclass automatically inherits *ALL* the methods of the superclass. All you can do in a subclass is modify a method’s implementation or add new methods, but you cannot remove methods.

    The only way to create a class which has a specific set of methods is to *NOT* to inherit from a superclass which has more methods than you actually need. I’m afraid that the benefits of inheritance far outweigh the non-benefits of this ridiculous principle.

  2. reestyle Says:

    It makes a whole lot of sense to me.

    @tony: I think what your concept of subclassing is, is rather the other way around. You have a ‘universal adaptation’ (the abstract class), that contains all the regular methods, but can call a method available in the subclass.

    For instance a DB class (which I think is a perfect example here): the insert, update and delete methods should be in the abstract class so subclasses, derived from the abstract class, are merely an adaptation (DB specific way of creating a select or otherwise) for your DB.

  3. tonymarston Says:

    I am unclear on how this principle can be implemented in PHP. Supposing I have a class which has 10 methods, and I have 10 clients which access that class, but only use 1 method each, how can I create a subclass for each client that only contains the method used by that client?

  4. thepercival Says:

    yes, I can see the thruth in these principles. They are already pretty advanced though. Lots of basic rules to follow first.

    greetz