Black Friday Black Friday
Black Friday ultimate sale! Get up to 80 % extra free points! More information here

Lesson 5 - Wrong ways of passing dependencies - Singleton and SL

Software design Software architectures and dependency injection Wrong ways of passing dependencies - Singleton and SL

In the previous lesson, Wrong ways of passing dependencies - Static members, we introduced several ways to deal with dependencies in multi-layered applications. We already know that if the entire application is written in a single file, it's barely readable, if not even impossible to create due to the complexity of today's software. Once we separate the application into multiple layers and layers into objects, we'll necessarily have to let these objects communicate with others. When an object communicates with another, we say it has dependencies.

In today's software design tutorial, we're going to talk about the Singleton design pattern which is related to static members. We discussed the static members last time. Next, we're gonna show the Service Locator pattern.

Mistake no. 3 - Passing dependencies as Singletons

Singleton is a rather controversial design pattern from the popular GOF design pattern group. Although you can see it in an example here, you can also find a detailed article about it here in case you want more information.

Singleton is basically similar to our static wrapper from the last lesson. It's important to say that Singleton uses static members, stores an instance of the dependency in a static attribute and provides a static method to get it. So, it has completely all the weaknesses related to static members we've already mentioned. Moreover, it's quite difficult to create a true Singleton, for example, in PHP. The fact that it's a design pattern does not make it any better and it's at least an anti-pattern when talking about using it for dependency management. In my opinion, it's less appropriate to use Singletons than pure static members. But it's more a matter of personal taste. Singletons can be written to be thread-safe so they can find their place when working with threads where it makes sense using them. But they are not good for dependencies.

Let's show how passing a connected PDO instance (database) to our models would look like in the car application.

Models/Database.php

We'll implement Singleton to get the database instance:

class Database
{
    private static $pdo;

    private function __construct() { /* Empty */ }

    public static function getInstance()
    {
        if (!self::$pdo)
            self::$pdo = new PDO('mysql:host=localhost;dbname=testdb;charset=utf8', 'root', '');
        return self::$pdo;
    }

    final public function __clone()
    {
        throw new Exception('Cloning is not allowed');
    }

    final public function __wakeup()
    {
        throw new Exception('Deserialization is not allowed');
    }
    
}

At first glance, we can see a static attribute with the PDO instance in the class as it was in the wrapper from the previous lesson. However, we don't wrap the PDO methods, but only provide a single method to get the entire instance. Note the lazy-loading, causing that the instance is created only when we ask for it and then just this one instance is always returned. Both the attribute with the PDO and the getInstance() method are static so they could be accessed from anywhere. But what if someone created a new database by writing new Database()? It doesn't make sense to create a Singleton instance since Singletons only serve to retrieve their static attribute contents. Therefore, we disable creating Database class instances by adding a private constructor. In PHP, there are other ways to create instances, the clone command and by deserialization. We need to disable them as well. You can see that our Singleton is getting a bit magical.

Models/CarMana­ger.php

In the model, we simply ask for the instance using the static method and then store it:

class CarManager
{
    private $database;

    public function __construct()
    {
        $this->database = Database::getInstance();
    }

    public function getCars()
    {
        return $this->database->query("SELECT * FROM cars")->fetchAll(PDO::FETCH_ASSOC);
    }

}

It's already been said that we still have all the weaknesses of static members. And the code is even longer than that in the static wrapper example, which looked like this:

class CarManager
{

    public function getCars()
    {
        return Database::queryAll("SELECT * FROM cars");
    }

}

Singleton doesn't really help us very much.

Controllers/Car­Controller.php

The controller will be identical. We'll create a new manager, and we don't need to pass anything to it because it will get the database by itself:

class CarController
{
    public function all()
    {
        $carManager = new CarManager();
        $cars = $carManager->getCars(); // A template variable
        require('Templates/cars.phtml'); // Loads the template
    }
}

Approach problems

All the problems associated with static members remain:

  • The database is accessible from anywhere, i.e. from controllers, but also, for example, from templates! From any PHP file that does not even need it. This is not very safe and it leads to poor class design and Low Coupling pattern violations.
  • The database will be difficult to mock (to replace it with testing data), which will make testing uncomfortable when the application becomes larger.
  • The application "lies" about its dependencies, at first sight, it's not obvious which classes are used by which classes. To find out, we need to go through the entire source code and look for static accesses.
  • A big problem might be using static members in multi-threaded applications. With PHP, you most likely won't get into this situation, but in other languages, ​​you'll enter race condition. The Singleton would have to be written as thread-safe, which is not in our case.

We have other problems here:

  • Unlike in the static wrapper, we need to store the instance somewhere, which makes the code unnecessarily longer
  • We need to ensure that there is always only a single instance

And one advantage:

  • We don't need to wrap all the PDO methods

It's clearly not a good idea to use Singleton to pass dependencies.

Mistake no. 4 - Gathering dependencies in a Service Locator

It might crossed your mind to create a container and store all your dependencies for the entire application in there, passing the container everywhere. The objects would then get what they need from the container. This approach is used, for example, in the MonoGame game framework for C#.NET or in the Ruby language.

So we'll create a class representing the container, store an instance of our database in there and then pass this container everywhere manually. Even if there would be lots of dependencies, we'd still deal with just a single dependency to the container. This should work better, right? Well... let's try that:

Models/Service­Locator.php

Let's prepare the container with shared services:

class ServiceLocator
{
    private $database;

    private __construct()
    {
        // Could be lazy-loaded as well
        $this->database = new PDO('mysql:host=localhost;dbname=testdb;charset=utf8', 'root', '');
    }

    public function getDb()
    {
        return $this->$database;
    }
}

index.php

Somewhere at the beginning of the application, we'll instantiate the container, and then we have to pass it everywhere manually:

// Autoloader
// ...

$locator = new ServiceLocator(); // // Creates the locator and the dependencies in it

// Code to create the controller ...
$controller = new $controllerName($locator); // Passing the locator to the controller

// ...

The code is similar to index.php from the Monolithic and two-tier architecture lesson. We passed the database this way. Here we pass the locator which could contain multiple dependencies.

Controllers/Car­Controller.php

In the controller, we'll store the container again and pass it to all the models, in our case, to the CarManager:

class CarController
{
    private $locator;

    public function __construct(ServiceLocator $locator)
    {
        $this->locator = $locator;
    }

    public function all()
    {
        $carManager = new CarManager($locator);
        $cars = $carManager-> getCars(); // A template variable
        require('Templates/cars.phtml'); // Loads the template
    }
}

Models/CarMana­ger.php

We'll do the same thing again in the model. We'll retrieve the locator and get the services we need from it:

class CarManager
{
    private $database;

    public function __construct(ServiceLocator $locator)
    {
        $this->database = $locator->getDb(); // Gets the database from the locator
    }

    public function getCars()
    {
         return $this->database->query("SELECT * FROM cars")->fetchAll(PDO::FETCH_ASSOC);
    }

}

Approach problems

Where are we after modifying the application to use a Service Locator?

  • All our models have access to all the services in the locator. This is a bit safer than using static members or Singletons, but it's still not the ideal state.
  • We still need to pass the locator instance and store its services.

In the end, the locator is quite the same as static members, we got some advantages but also other disadvantages.

Let's no longer bother with it. In the next lesson, Passing dependencies with Dependency Injection, there's the breaking point. We'll find a common problem of all the presented approaches and its solution. We'll introduce Inversion of Control and the long-promised Dependency Injection.


 

 

Article has been written for you by David Capka
Avatar
Do you like this article?
No one has rated this quite yet, be the first one!
The author is a programmer, who likes web technologies and being the lead/chief article writer at ICT.social. He shares his knowledge with the community and is always looking to improve. He believes that anyone can do what they set their mind to.
Unicorn College The author learned IT at the Unicorn College - a prestigious college providing education on IT and economics.
Activities (4)

 

 

Comments

To maintain the quality of discussion, we only allow registered members to comment. Sign in. If you're new, Sign up, it's free.

No one has commented yet - be the first!