In the previous article, the definitions of both the objects and the data structures were introduced. Also, the differences between both were discussed and why mixing them in one class would produce hybrid structures that inherit the bad parts of the both worlds. Then, we touched on the differences between the procedural and the OO programming approaches and how to pick one over the other according to the needs. Finally, we discussed the Law of Demeter, and how to make the code follow it gradually.

In this article, we are going to discuss classes; the higher level of code organization. How to organize a class? How big should a class be? What are the SOLID principles? And how could they be applied? Let's answer these question.

Organizing a Class

Each language (or even company/organization) might have its own organizational convention. However, if it is not presented, the following convention seems to be effective in most of the cases.

At the top of the class, we start with constants, followed by private variables (static first, then instance), then the public variables (static then instance as well), if any. Following the variables is the constructor (or a list of constructors if supported by the language), then the public methods of the class. Now what might really differ is where to place the private utility methods. Some languages recommend putting them at the bottom (as a convention), while other won't enforce any convention. In that case, it is recommended to place the private methods following the public one that is using it. In that way, reading a class will feel more natural and a story-like reading.

While some languages embrace (entirely or partially) the access modifiers, others don't. However, there is always a convention (or even a design pattern) to follow. For instance, in JavaScript, the "revealing module pattern" is used to expose/reveal parts of the implementation while hiding the others. In Python, the convention is to prefix the member with an underscore or use a mechanism known as "name mangling".

According to the documentation: “Private” instance variables that cannot be accessed except inside an object don’t exist in Python. However, there is a convention that is followed by most Python code: a name prefixed with an underscore (e.g. _spam) should be treated as a non-public part of the API (whether it is a function, a method or a data member). It should be considered an implementation detail and subject to change without notice.
Since there is a valid use-case for class-private members (namely to function name clashes of names with names defined by subclasses), there is limited support for such a mechanism, called name mangling. Any identifier of the form __spam (at least two leading underscores, at most one trailing underscore) is textually replaced with _classname__spam, where classname is the current class name with leading underscore(s) stripped. This mangling is done without regard to the syntactic position of the identifier, as long as it occurs within the definition of a class.

Class Size

Before questioning the size of a class, we need to define what is known as "God class". A God class is a class that does too much, have a lot of dependencies, knows too much and control many other classes, and has a lot of responsibilities. This type of class is quite a mess to deal with. It is hard to debug, unit test, and maintain. It tends to grow to a level that it becomes hard to replace in the future, which violates the modular nature of Object-Oriented Design.

Classes should be small by design, and this idea was emphasized upon functions before. The size of the function was measured by the number of lines, but this way of measuring is not applicable to the class. How then can we gauge the size of the class? Well, by the number of the responsibilities.

Defining the responsibility of the class goes one-on-one with its name. That's because the name should indicate what does the class do as mentioned at the beginning of this series when discussing naming. Therefore, if the class does too much, then the name could not potentially give an indication of the responsibility of that class. If a name could not be derived easily out of the responsibility of the class, then the class is large and doing more than it is supposed to.

S.O.L.I.D.

SOLID
"SOLID" is an acronym for the five most known Object-Oriented Design principles by Michael Feathers, which are promoted by Robert C. Martin in his books. These principles, among others, tend to make software designs better, flexible, and more maintainable. These five principles are:

  • SRP: Single Responsibility Principle.
  • OCP: Open/Closed Principle.
  • LSP: Liskov Substitution Principle.
  • ISP: Interface Segregation Principle.
  • DIP: Dependency Inverion Principle.

Let's take each one of them and explain it with some examples.

Single Responsibility Principle

A class should have only one reason to change.

This principle simply states that a class or a module should have only a single responsibility and the functionality of it should be entirely encapsulated by that class/module. As simple as that sounds, it is easy to overlook it. The single responsibility principle enforces the small classes idea that was discussed earlier.

Mistakenly, "one responsibility" could be translated as "a class should have one method." Although it might, that is not what is meant in this context. It means that the behavior should be related to the class responsibility. Let's take an example.

namespace App;

use Database;

class Product
{
    public function __construct(Database $db)
    {
        $this->db = $db;
    }

    public function onSale()
    {
        // Get the products that are on sales
        $products = $this->getProductsOnSale();

        // Return formatted results
        return $this->format($products);
    }

    private function getProductsOnSale()
    {
        return $this->db->table('products')->where('sales', '=', 'yes')->all();
    }

    private function format($products)
    {
        return json_encode($products);
    }
}

Apparently, the SRP has been violated because this class has more than one responsibility, namely connecting to the database, fetching data, and returning data as JSON. The way to fix this is to extract some of these responsibilities to their classes.

One way to look at the SRP is the "one reason to change," which doesn't apply in the previous class. For instance, if we want to modify the persistence layer in the future, we would have to change the getProductsOnSale() implementation, therefore the class is changed. The same if we want to modify the representation to be XML instead of JSON in the format($products). Now we have two reasons to change violating the SRP.

It is not the Product class's responsibility to know about the persistence layer or how to fetch the information from the DB. So let's start by fixing that.

We would have to create another repository class that would have a method which takes care of fetching the data from the database and returns the results.

namespace App\Repository;

use Database;

class ProductRepository
{
    public function __construct(Database $db)
    {
        $this->db = $db;
    }

    public function onSale()
    {
        return $this->db->table('products')->where('sales', '=', 'yes')->all();
    }
}

We moved the getProductsOnSale() to the ProductRepository class and rename it to simply onSale(). Now we update the Product class to be like the following.

namespace App;

use App\Repository\ProductRepository;

class Product
{
    public function __construct(ProductRepository $repo)
    {
        $this->repo = $repo;
    }

    public function onSale()
    {
        // Get the products that are on sales
        $products = $this->repo->onSale();

        // Return formatted results
        return $this->format($products);
    }

    private function format($products)
    {
        return json_encode($products);
    }
}

It is much cleaner and more maintainable. We can change the repository implementation of the onSales() method without having to change anything in the Product class.

Next on the list is the format method. It is not the Product class's responsibility to format the results. Moreover, what if we needed to change the format to be XML representation or even a plain PHP array? Let's fix this.

First, we would create an interface that will be the contract for any class that wants to format a set of data.

namespace App;

interface Formatter
{
    public function format($data);
}

Then, we could create as many classes as we need to define formatting mechanisms; JSON, XML or what have you.

namespace App;

use Formatter;

class JSONProductsFormatter implements Formatter
{
    public class format($products)
    {
        return json_encode($products);
    }
}

The class is basic and straight forward at this stage, yet we could have XMLProductsFormatter class and such. Now the Product class can accept the interface and apply the format method with the result leaving the representation of the output to the consumer.

namespace App;

use App\Repository\ProductRepository;
use App\Formatter;

class Product
{
    public function __construct(ProductRepository $repo)
    {
        $this->repo = $repo;
    }

    public function onSale(Formatter $formatter)
    {
        // Get the products that are on sales
        $products = $this->repo->onSale();

        // Return formatted results
        return $formatter->format($products);
    }
}

Now the onSale method would not care what format the cosumer request as long as it implements the Formatter interface.

Open/Closed Principle

Software entities should be open for extension, but closed for modification.

The OCP states that classes, modules, functions and such should be extensible without the need to modify their code. It is not that simple to grasp without an example.

In an example when discussing the difference between the Procedural and Object-Oriented Programming, we had three classes representing geometrical shapes and a fourth class, Geometry, which calculates the area depending on the shape.

class Circle  
{
    public $center;
    public $radius;
}

class Square  
{
    public $topLeft;
    public $side;
}

class Rectangle  
{
    public $topLeft;
    public $height;
    public $width;
}

class Geometry  
{
    const PI = 3.14;

    public function area($shape)
    {
        if ($shape instanceof Circle) {
            return self::PI * $shape->radius * c.radius;
        } else if ($shape instanceof Square) {
            return $shape->side * $shape->side;
        } else if ($shape instanceof Rectangle) {
            return $shape->height * $shape->width;
        }
        throw new Exception("No Such Shape");
    }
}

As noticeable, and mentioned before, the Geometry class is checking the $shape against the defined shapes, Circle, Square, and Rectangle, and calculate the area according to it.

Now if we are required to add a new shape, say triangle, for instance, we would have to go, and modify the area method on the Geometry class. This would violate the OCP because we would have to change the code.

Fixing that was intoduced later as the OO approach.

interface Shape
{
    public function area();
}

class Circle implements Shape  
{
    const PI = 3.14;

    private $center;
    private $radius;

    public function area()
    {
        return self::PI * $this->radius * $this->radius;
    }
}

class Square implements Shape  
{
    private $topLeft;
    private $side;

    public function area()
    {
        return $this->side * $this->side;
    }
}

class Rectangle implements Shape  
{
    private $topLeft;
    private $height;
    private $width;

    public function area()
    {
        return $this->height * $this->width;
    }
}

What we did is placing the extensible behavior behind an interface, in this case, is the Shape, and inverse the dependency. This is basically the OCP.

Liskov Substitution Principle

Let q(x) be a property provable about objects of x of type T. Then q(y) should be provable for objects y of type S where S is a subtype of T.

This principle initially introduced by Barbara Liskov in a 1987 conference keynote address titled Data abstraction and hierarchy. It's a wordy definition, and I didn't grasp that at first.

Uncle Bob gave it the next definition.

Subtypes must be substitutable for their base types.

To break it down, LSP states that the subclasses of a class should be substitutable for their base/original class. That means if we have a class Father, and a class Child that extends the class Father, then we should be able to substitute Child for Father anywhere Father is being used. As simple as that sounds, it might be a bit difficult to apply it. The following example would illustrate that.

Let's assume that have a gallery application, and we need to return a list of the images saved either on Dropbox or in a database (regardless why we shouldn't save our images in DB). We have the ImageDropbox class extends ImageDB and re-implement the getAll() method.

class ImageDB
{
    public function getAll()
    {
        // return collections (Laravel's Eloquent) from Database
    }
}

class ImageDropbox extends ImageDB
{
    public function getAll()
    {
        // return array of images that are stored on Dropbox
    }
}

function zipImages($images)
{
    // Zip images
}

As LSP states, we should be able to use getAll() on both ImageDropbox and ImageDB interchangeable on the zipImages($images). However, the getAll() is returning Laravel's Eloquent collections on ImageDB, while returning an array
on ImageDropbox. This is a violation of the "Liskov Substitution Principle" because we cannot substitute the results of getAll() on ImageDB and ImageDropbox.

To fix this, once again, we are going to create a repository interface, ImageReposity, with a getAll() signature, and the two classes, ImageDropbox and ImageDB, implement getAll().

interface ImageRepository
{
    public function getAll(): array;
}

class ImageDropbox implements ImageRepository
{
    public function getAll(): array
    {
        // return array of images that are stored on Dropbox
    }
}

class ImageDB implements ImageRepository
{
    public function getAll(): array
    {
        // return collections (Laravel's Eloquent) from Database
    }
}

function zipImages(array $images)
{
    // Zip images
}

Using PHP7's "scalar type declarations," and "return type declarations", we would enforce the returned type to be of an array.

Interface Segregation Principle

A client should never be forced to implement an interface that it doesn't use or clients shouldn't be forced to depend on methods they do not use. This is a relatively straightforward principle to understand.

It is noticeable by now that we rely a lot on our interfaces, hence the say "program to an interface". Interfaces almost have the same scope of responsibility, whether they are interface typed entities or objects implementing design patterns like Facades. That scope is acting as contracts to the client code on using an individual module. But enough of that, let's jump to the example.

interface Worker {
	public function work();
	public function eat();
}

class NormalWorker implements Wroker{
	public function work()
    {
		// working
	}
	public function eat()
    {
		// eating
	}
}

class SuperWorker implements Wroker{
	public function work()
    {
		// working much more
	}

	public function eat()
    {
		// eating
	}
}

class Manager {
	Wroker worker;

	public function setWorker(Worker w)
    {
		worker = w;
	}

	public function manage() {
		worker.work();
	}
}

This is a classical example. We have a Manager class which manages Workers. Workers are two types; NormalWorker, and SuperWorker. Both of those types work and eat. Later on, a need for a new type of workers has been introduced; the RobotWroker. As one might assume, that type doesn't eat, it just works. Implementing the eat method on the RobotWorker will violate the IS Principle. To fix that, we split the Worker interface into two interfaces Workable and Feedable which means that the RobotWorker class won't have to implement the Feedable interface.

interface Worker extends Feedable, Workable {
}

interface Workable {
	public function work();
}

interface Feedable{
	public function eat();
}

class NormalWorker implements Workable, Feedable{
	public function work() {
		// working
	}

	public function eat() {
		// eating
	}
}

class RobotWorker implements Workable{
	public function work() {
		// working
	}
}

class SuperWorker implements Workable, Feedable{
	public function work() {
		// working much more
	}

	public function eat() {
		// eating
	}
}

class Manager {
	Workable worker;

	public function setWorker(Workable w) {
		worker = w;
	}

	public function manage() {
		worker.work();
	}
}

Dependency Inversion Principle

High-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend upon details. Details should depend upon abstractions.

The core of this principle is decoupling code because this what differentiate clean code from bad one. Dependency Injection is what this principle depends on. However, it is merely the only thing that we need to understand here. Straight to the example.

class ProductRepository
{
    private $db;

    public function __construct(MongoDBConnection $db)
    {
        $this->db = $db;
    }
}

The ProductRepository knows too much about the connection method to the database; it depends on it to perform an action. "High-level modules should not depend on low-level modules. Abstractions should not depend upon details." In this case ProductRepository (high-level module) depends on MongoDBConnection (low-level module, and details).
But wait! Isn't that the Dependency Injection that we just mentioned it'll help us? Well, as I mentioned, it is merely the only thing we need to take care of.

So once again, we would use the help of an interface.

interface DBConnectionInterface
{
    public function connect();
}

class ProductRepository
{
    private $db;

    public function __construct(DBConnectionInterface $db)
    {
        $this->db = $db;
    }
}

Now the high-level module depends on abstraction and not detailed low-level module. But we still have this part: "Both (high-level, and low-level modules) should depend on abstractions."

interface DBConnectionInterface
{
    public function connect();
}

class MongoDBConnection implements DBConnectionInterface 
{
    public function connect()
    {
        // Connect to MongoDB
    }
}

class ProductRepository
{
    private $db;

    public function __construct(DBConnectionInterface $db)
    {
        $this->db = $db;
    }
}

Now we have both the high-level module (ProductRepository) and the low-level module (MongoDBConnection) depend on abstraction and not details. ProductRepository by injecting DBConnectionInterface into __construct and MongoDBConnection by implementing DBConnectionInterface.