S.O.L.I.D: 5 Principles of Object-Oriented Design

S.O.L.I.D: 5 Principles of Object Oriented Design

SOLID is a set of object oriented design principles aimed at
making code more maintainable and flexible. They were coined by Robert “Uncle
Bob” Martin in the year 2000 in his paper Design Principles and Design
Patterns
. The SOLID principles apply to any object oriented language.

SOLID is an acronym for five object-oriented design
principles.

S              —
Single responsibility principle

O             —
Open-closed principle

L              —
Liskov substitution principle

I               —
Interface segregation principle

D             —
Dependency Inversion Principle

The intention behind these five principles is to promote
flexible, understandable and maintainable code.

 

Single-responsibility Principle

This states that a class should have a single responsibility,
but more than that, a class should only have one reason to change.

Taking an example of the (simple) class called Page.

class Page {
 protected $title;
 
 public getPage($title) {
 return $this->title;
 }
 
 public function formatJson() {
 return json_encode($this->getTitle());
 }
}

This class knows about a title property and allows this
title property to be retrieved by a get () method. We can also use a method in
this class called formatJson() to return the page as a JSON string. This might
seem like a good idea as the class is responsible for its own formatting.

 

What happens, however, if we want to change the output of
the JSON string, or to add another type of output to the class? We would need
to alter the class to either add another method or change an existing method to
suit. This is fine for a class as simple as this, but if it contained more
properties then the formatting would be more complex to change.

A better approach to this is to modify the Page class
so that is only knows about the data is handles. We then create a secondary
class called JsonPageFormatter that is used to format
the Page objects into JSON.

class Page {
  protected $title;
 
  public getPage($title){
    return $this->title;
  }
}
 
class JsonPageFormatter {
    public function format(Page $page) {
        return json_encode($page->getTitle());
    }
}

Doing this means that if we wanted to create an XML format
we could just add a class called 
XmlPageFormatter and write
some simple code to output XML. We now have only one reason to change the 
Page class.

 

 

Open/Closed Principle

In the open/closed principle classes should be open
for extension, but closed for modification
. Essentially meaning that
classes should be extended to change functionality, rather than being altered.

As an example, take the following two classes.

class Rectangle {
  public $width;
  public $height;
}
 
class Board {
  public $rectangles = [];
  public function calculateArea() {
    $area = 0;
    foreach ($this->rectangles as $rectangle) {
      $area += $rectangle->width * $rectangle->height;
    }
  }
}

We have a Rectangle class that contains the
data for a rectangle, and a 
Board class that is used as a
collection of 
Rectangle objects. With this setup we can easily
find out the area of the board by looping through the items in the $rectangles
collection and calculating their area.

 

The problem with this setup is that we are restricted by the
types of object we can pass to the Board class. For example,
if we wanted to pass a Circle object to the Board class
we would need to write conditional statements and code to detect and calculate
the area of the Board.

The correct way to approach this problem is to move the area
calculation code into the shape class and have all shape classes extend a Shape interface.
We can now create a Rectangle and Circle shape
classes that will calculate their area when asked.

 

interface Shape {
   public function area();
}
 
class Rectangle implements Shape {
  public function area() {
    return $this->width * $this->height;
  }
}
 
class Circle implements Shape {
  public function area() {
    return $this->radius * $this->radius * pi();
  }
}

The Board class can now be reworked so that
it doesn’t care what type of shape is passed to it, as long as they implement
the area() method.

 

class Board {
  public $shapes;
 
  public function calculateArea() {
    $area = 0;
    foreach ($this->shapes as $shape) {
      $area+= $shape->area();
    }
    return $area;
  }
}

We have now setup these objects in a way that means we don’t
need to alter the 
Board class if we have a different type of
object. We just create the object that implements Shape and pass it
into the collection in the same way as the other classes.

 

 

Liskov Substitution Principle

Created by Barbara Liskov in a 1987, this states that objects
should be replaceable by their subtypes without altering how the program works
.
In other words, derived classes must be substitutable for their base classes
without causing errors.

The following code defines a Rectangle class
that we can use to create and calculate the area of a rectangle.

class Rectangle {
  public function setWidth($w) { 
      $this->width = $w;
  }
 
  public function setHeight($h) {
      $this->height = $h;
  }
 
  public function getArea() {
      return $this->height * $this->width;
  }
}

Using that we can extend this into a Square class. Because a
square a little different from a rectangle we need to override some of the code
in order to allow a Square to exist correctly.

 

class Square extends Rectangle {
  public function setWidth($w) {
    $this->width = $w;
    $this->height = $w;
  }
 
  public function setHeight($h) {
    $this->height = $h;
    $this->width = $h;
  }
}

This seems fine, but ultimately a square is not a rectangle
and so we have added code to force this situation to work.

 

A good analogy that I read once was to think about a Duck
and a Rubber Duck as represented by classes. Although it is possible to extend
a Duck class into a Rubber Duck class we would need to override a lot of Duck
functionality to suit the Rubber Duck. For example, a Duck quacks, but a Rubber
Duck doesn’t (ok, maybe it squeaks a bit), A Duck is alive, but a Rubber Duck
isn’t.

Overriding lots of code in classes to suit specific
situations can lead to maintenance problems.

One solution to the rectangle vs. square situation is to
create an interface called Quadrilateral and implement this in
separate Rectangle and Square classes. In
this situation we are allowing the classes to be responsible for their own
data, but enforcing the need for certain method footprints being available.

 

 

interface Quadrilateral {
  public function setHeight($h);
 
  public function setWidth($w);
 
  public function getArea();
}
 
class Rectangle implements Quadrilateral;
 
class Square implements Quadrilateral;

 Interface Segregation Principle

This states that many client-specific interfaces are
better than one general-purpose interface
. In other words, classes should
not be forced to implement interfaces they do not use.

Let’s take an example of a Worker interface.
This defines several different methods that can be applied to a worker at a
typical development agency.

interface Worker {
 
  public function takeBreak()
 
  public function code()
 
  public function callToClient()
 
  public function attendMeetings()
 
  public function getPaid()
}

The problem is that because this interface is too generic we
are forced to create methods in classes that implement this interface just to
suit the interface.

 

For example, if we create a Manager class then we are forced
to implement a code () method because that’s what the interface requires.
Because managers generally don’t code we can’t actually do anything in this
method so we just return false.

class Manager implements Worker {
  public function code() {
    return false;
  }
}

Also, if we have a Developer class that implements Worker
then we are forced to implement a callToClient() method because that’s what the
interface requires.

 

class Developer implements Worker {
  public function callToClient() {
    echo "I'll ask my manager.";
  }
}

Having a fat and bloated interface means having to implement
methods that do nothing.

 

The correct solution to this is to split our interfaces into
separate parts, each of which deals with specific functionality. Here, we split
out a Coder and ClientFacer interface from our generic Worker interface.

interface Worker {
  public function takeBreak()
  public function getPaid()
}
 
interface Coder {
  public function code()
}
 
interface ClientFacer {
  public function callToClient()
  public function attendMeetings()
}

 

With this in place we can implement our sub-classes without
having to write code that we don’t need. So our Developer and Manager classes
would look like this.

class Developer implements Worker, Coder {
}
 
class Manager implements Worker, ClientFacer {
}

 

Having lots of specific interfaces means that we don’t have
to write code just to support an interface.

Dependency Inversion Principle
Perhaps the simplest of the principles, this states that classes should depend
upon abstractions, not concretions. Essentially, don’t depend on concrete
classes, depend upon interfaces.

Taking an example of a PageLoader class that uses a
MySqlConnection class to load pages from a database we might create the classes
so that the connection class is passed to the constructor of the PageLoader
class.

class MySqlConnection {
    public function connect() {}
}
 
class PageLoader {
    private $dbConnection;
    public function __construct(MySqlConnection $dbConnection) {
        $this->dbConnection = $dbConnection;
    }
}

This structure means that we are essentially stuck with
using MySQL for our database layer. What happens if we want to swap this out
for a different database adaptor? We could extend the MySqlConnection class in
order to create a connection to Memcache or something, but that would
contravene the Liskov Substitution principle. Chances are that alternate
database managers might be used to load the pages so we need to find a way to
do this.

 

The solution here is to create an interface called
DbConnectionInterface and then implement this interface in the MySqlConnection
class. Then, instead of relying on a MySqlConnection object being passed to the
PageLoader class, we instead rely on any class that implements the
DbConnectionInterface interface.

interface DbConnectionInterface {
    public function connect();
} 
 
class MySqlConnection implements DbConnectionInterface {
    public function connect() {}
}
 
class PageLoader {
    private $dbConnection;
    public function __construct(DbConnectionInterface $dbConnection) {
        $this->dbConnection = $dbConnection;
    }
}

 

With this in place we can now create a MemcacheConnection
class and as long as it implements the DbConnectionInterface then we can use it
in the PageLoader class to load pages.

This approach also forces us to write code in such a way
that prevents specific implementation details in classes that don’t care about
it. Because we have passed in a MySqlConnection class to our PageLoader class
we shouldn’t then write SQL queries in the PageLoader class. This means that
when we pass in a MemcacheConnection object it will behave in the same way as
any other type of connection class.

When thinking about interfaces instead of classes it forces
us to move that specific domain code out of our PageLoader class and into the
MySqlConnection class.

How to Spot It?
A bigger question might be how you can spot if you need to apply SOLID
principles to your code or if you are writing code that isn’t SOLID.

Knowing about these principles is only half of the picture,
you also need to know when you should step back and think about applying SOLID
principles. I came up with a quick list of things you need to keep an eye on
that are ‘tells’, showing that your code might need to be re-architecture.

You’re writing a lot of “if” statements to handle different
situations in object code.
You’re writing a lot of code that doesn’t actually do anything just to satisfy
interface design.
You keep opening the same class to change the code.
You are writing code in classes that don’t really have anything to do with that
class. For example, putting SQL queries in a class outside the database
connection class.

 

Conclusion

SOLID isn’t a perfect methodology, and can lead to complex
applications with many moving parts, and occasionally lead to writing code just
in case it’s needed. Using SOLID mean writing more classes and creating more
interfaces, but many modern IDE’s will solve that problem.

If you need help in any kind of development project & I
can also provide you consultancy about your project. I am top rated Freelancer. You can hire me directly
on Upwork. You can also hire me on Freelancer.

If you have any comment, question, or recommendation, feel
free to post them in the comment section below!

5 1 vote
Article Rating
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments

Let’s Socialize

Popular Post

Share

Share on facebook
Facebook
Share on twitter
Twitter
Share on linkedin
LinkedIn
Share on tumblr
Tumblr
Share on skype
Skype
Share on whatsapp
WhatsApp

Flutter vs. React Native

Flutter: Flutter is a UI toolkit used for crafting beautiful, natively compiled applications for mobile, web, and desktop from a single codebase. Flutter is not

Read More »
0
Would love your thoughts, please comment.x
()
x