In the previous article of this series, we discussed how to write clean functions. Functions should be small, do one thing, and don't contain any repetitive logic. Functions should not introduce side effects and function's arguments should not be boolean flags.

In this article, we'll discuss objects and data structures. This will profoundly influence in the next article when discussing classes and the best practices of Object-Oriented Design. This article will touch on that as well and show some concepts of the OO.

Examples in this section will be written in PHP, yet they are applied in most of the other programming language.

Data Abstraction and Objects

When starting a new system/project that is object-oriented driven, we usually tend to think of the classes and objects that the domain introduce as a first step of the analyzing process. For instance, when building an e-commerce system, some of the objects could be customer, product, order, shopping cart.

Later on, when implementing those objects, the first thing most of the developers will do is creating getters and setters exposing their private variables. This doesn't make much sense because our intention when creating those private variables is not allowing others to depend on them. They are defined private for that reason.

Robert C. Martin's Clean Code suggests that what we need to achieve is prevent exposing the details of the data by providing it in an abstract term, which is not readily achievable and requires some serious consideration of the best way an object represents its data. Hiding implementation is not as simple as placing a layer of functions (getters and setters) between the variables. Hiding implementation is about abstraction. Let's take the following example.

class Point()
{
    private $x;
    private $y;

    public function setX($x)
    {
        $this->x = $x;
    }

    public function setY($y)
    {
        $this->y = $y;
    }

    public function getX()
    {
        return $this->x;
    }

    public function getY()
    {
        return $this->y;
    }
}

This class is an implementation of a point in rectangular coordinates, and it enforces manipulation of those coordinates independently. This exposes the implementation, even with the getters and setters. The implementation is concrete, and the way we want our data structure to be is abstract.

Interface Point
{
    public function getX();
    public function getY();
    public function setCartesian($x, $y);
    public function getR();
    public function getTheta();
    public function setPolar($r, $theta);
}

The interface represents more than an abstract data structure. We must set the coordinates together (the methods enforce that), but we can read the individual coordinates independently.

"A class does not simply push its variables out through getters and setters. Rather it exposes abstract interfaces that allow its users to manipulate the essence of the data, without having to know its implementation." - Robert C. Martin.

Object Oriented and Procedural Programming

Now the line that separates the data structure and objects has been drawn. Data structure expose data and have no vital functions, while objects hide their data (abstraction) and expose functions that use the data. Data structure and objects complement each other. This is trivial to understand the next bit. Consider the following example.

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");
    }
}

Mistakenly, some of the developers would think that the previous code is object-oriented where the fact is that it is procedural. If we needed to add a new function such as perimeter(), we have to change the Geometry class and none of the shape classes would be affected. However, if we add a new shape class, we must change all the functions in the Geometry class. Let's take the OO approach.

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;
    }
}

The area() function is polymorphic. In this case, if we add a new shape class, none of the other shape classes would be affected. On the other hand, if we add a new function, all of the shape classes must change. It is important to understand this point. It represents the conceptual difference between OO and Procedural Programming. It is the complementary nature of the data structure and objects.

Procedural code (data structure): easy to add new functions to existing data structures, and hard to add new data structures because the all the functions must change.

Object-oriented code: easy to add new classes without modifying existing functions, and hard to add new functions because all the classes must change.

Law of Demeter (a.k.a. Tell, Don't Ask)

We know by now that an object should hide its data and expose functionality. This means an object should not expose its internal structure. The Law of Demeter claims that a method on an object is allowed to interact with the following methods on:

  • The same object.
  • Properties of the same object.
  • Arguments (objects) passed into it (the method).
  • Instances of new objects created inside of it (the method).

Let's check the following example which violates the Law of Demeter. Try to reason the previous claims and see if the violation parts are detectable.

class Person
{
    public function __construct($name, $drivingLicense = null)
    {
        $this->name = $name;
        $this->drivingLicense = $drivingLicense;
    }

    public function getName()
    {
        return $this->name;
    }

    public function getDrivingLicense()
    {
        return $this->drivingLicense;
    }
}

class DrivingLicense
{
    public function __construct($issueDate, $photo = null)
    {
        $this->issueDate = $issueDate;
        $this->photo = $photo;
    }

    public function getIssueDate()
    {
        return $this->issueDate;
    }

    public function getPhoto()
    {
        return $this->photo;
    }
}

class Photo
{
    public function __construct($path)
    {
        $this->path = $path;
    }

    public function getPath()
    {
        return $this->path;
    }
}

$photo = new Photo('./img/photo-387123.png');
$drivingLicense = new DrivingLicense('2017-01-01', $photo);
$person = new Person('Mike', $drivingLicense);

$personName = $person->getName();
$issueDate = '';
$photoPath = '';

if ($person->getDrivingLicense()) {
    $issueDate = $person->getDrivingLicense()->getIssueDate();
}

if ($person->getDrivingLicense() && $person->getDrivingLicense()->getPhoto()) {
    $photoPath = $person->getDrivingLicense()->getPhoto()->getPath();
}

echo "{$personName}'s license has issued on {$issueDate}, and the photo could be found here: {$photoPath}";

The $person object is violating the first rule, which states that "a method can interact other methods on its object". That is addressed when we are getting the issue date (getIssueDate()) from the driving license here $issueDate = $person->getDrivingLicense()->getIssueDate();. This makes the caller knows a lot about the object internals. To solve this issue, we create a new method on the Person class which get the issue date for us.

public function getDrivingLicenseIssueDate()
{
    if ($this->drivingLicense) {
        return $this->drivingLicense->getIssueDate();
    }
}

We check the existence of the drivingLicense object and return the issue date if it does. Otherwise, we get null. Now we can delete the if statement that checks the driving license (if ($person->getDrivingLicense()) { ... }) and directly assign the issue date like so.

$issueDate = $person->getDrivingLicenseIssueDate();

This is much cleaner and simpler. However, the next problem is trickier as we have three nested level deep ($person->getDrivingLicense()->getPhoto()->getPath()), and ideally, the Law of Demeter requires that to be one at most. To solve this problem, we would create a new method that will get the license photo path.

public function getDrivingLicensePhotoPath()
{
    if ($this->drivingLicense && $this->drivingLicense->getPhoto) {
        return $this->drivingLicense->getPhoto()->getPath();
    }
}

Now we can get rid of the other if statement and set the $photoPath to getDrivingLicensePhotoPath(), but we still have a bit of an issue here. Now the Person class violates the Law of Demeter right here $this->drivingLicense->getPhoto()->getPath(). To solve that as well, we need to create a method on the DrivingLicense class which returns the photo path like so.

public function getPhotoPath()
{
    if ($this->photo) {
        return $this->photo->getPath();
    }
}

Now the getDrivingLicensePhotoPath() method on the Person class should be like the following.

public function getDrivingLicensePhotoPath()
{
    if ($this->drivingLicense && $this->drivingLicense->getPhoto()) {
        return $this->drivingLicense->getPhotoPath();
    }
}

Now our classes are respecting the Law of Demeter. We are telling the object what we want and not asking it what does it know before requesting what we want. Hence the name "Tell, Don't Ask."

Here's the final piece of code all put together.

class Person
{
    public function __construct($name, $drivingLicense = null)
    {
        $this->name = $name;
        $this->drivingLicense = $drivingLicense;
    }

    public function getName()
    {
        return $this->name;
    }

    public function getDrivingLicense()
    {
        return $this->drivingLicense;
    }

    public function getDrivingLicenseIssueDate()
    {
        if ($this->drivingLicense) {
            return $this->drivingLicense->getIssueDate();
        }
    }

    public function getDrivingLicensePhotoPath()
    {
        if ($this->drivingLicense && $this->drivingLicense->getPhoto()) {
            return $this->drivingLicense->getPhotoPath();
        }
    }
}

class DrivingLicense
{
    public function __construct($issueDate, $photo = null)
    {
        $this->issueDate = $issueDate;
        $this->photo = $photo;
    }

    public function getIssueDate()
    {
        return $this->issueDate;
    }

    public function getPhoto()
    {
        return $this->photo;
    }

    public function getPhotoPath()
    {
        if ($this->photo) {
            return $this->photo->getPath();
        }
    }
}

class Photo
{
    public function __construct($path)
    {
        $this->path = $path;
    }

    public function getPath()
    {
        return $this->path;
    }
}

$photo = new Photo('./img/photo-387123.png');
$drivingLicense = new DrivingLicense('2017-01-01', $photo);
$person = new Person('Mike', $drivingLicense);

$personName = $person->getName();
$issueDate = $person->getDrivingLicenseIssueDate();
$photoPath = $person->getDrivingLicensePhotoPath();

if ($person->getDrivingLicense() && $person->getDrivingLicense()->getPhoto()) {
    $photoPath = $person->getDrivingLicense()->getPhoto()->getPath();
}

echo "{$personName}'s license has issued on {$issueDate}, and the photo could be found here: {$photoPath}";

Say No To Hybrids

Sometimes, and as a hack, developers tend to mix both of the data structures and objects resulting in a half data structure and half object that is known as the hybrid structure. Those hybrid structures will have the worst of the both worlds. They make it hard to add new functions, and hard to add new data structures. Hybrids are the worst and should be avoided.

Conclusion

Hopefully, the difference between the data structures and objects is clearer at this stage. Again, objects expose behavior and hide data, while data structure reveals data and have no significant behavior. Before making a decision of whether it is a data structure or an object, stop and think what does it precisely represent, the abstraction of the data, and the behavior of the entity.