Singleton

Some blogs earlier I had an example for the singleton pattern. Here it is again (revised).

The singleton pattern is useful if you want or need an object only initiated once. If you are implementing some sort of cache or you’ve got an object with a database connection you might want to do this. There are also reasons to use singleton for the states in the state pattern. Anyway, let me show you how.

With a public constructor you can initialize an object as much as you like. Hence the constructor will be private:

class SingletonExample{
    private function __construct(){
    }
}

Now we do have a problem, without a public constructor, we cannot initiate the class. We need a static method to do that:

class SingletonExample{
    public static $instance = null;
    public static getInstance(){
        if(is_null(self::$instance)){
            self::$instance = new self();
        }
        return self::$instance;
    }
    private function __construct(){
    }
}

You would initiate the singleton using:

$st = SingletonExample::getInstance();

Now take a look at the following example:

class NonSingleton{
    private $text = 'hello world';
 
 
 
 
 
 
 
    public function __construct(){
    }
    public function setText($text){
        $this->text = $text;
    }
    public function getText(){
        return $this->text;
    }
}
class SingletonExample{
    private $text = 'hello world';
    public static $instance = null;
    public static function getInstance(){
        if(is_null(self::$instance)){
            self::$instance = new self();
        }
        return self::$instance;
    }
    private function __construct(){
    }
    public function setText($text){
        $this->text = $text;
    }
    public function getText(){
        return $this->text;
    }
}
$se = new NonSingleton();
echo $se->getText()."\n";
$se->setText('foo');
echo $se->getText()."\n";
 
$sa = new NonSingleton();
echo $sa->getText()."\n";
$se = SingletonExample::getInstance();
echo $se->getText()."\n";
$se->setText('foo');
echo $se->getText()."\n";
 
$sa = SingletonExample::getInstance();
echo $sa->getText()."\n";
result
hello world
foo
hello world
result
hello world
foo
foo

So yes, the singleton object keeps its data and is only initiated once. Now to avoid that another class might extend the singleton, the final-keyword has to be added. Furthermore we disallow cloning by defining it private. The whole singleton looks like this:

final class SingletonExample{
    public static $instance = null;
 
    public static getInstance(){
        if(is_null(self::$instance)){
            self::$instance = new self();
        }
 
        return self::$instance;
    }
 
    private function __construct(){
    }
 
    private function __clone(){
    }
}

Read my last post about the state pattern? Such states can be implemented as singleton to reduce resources (no need to reinitiate the states all the time when switching from idle to active and back – but exactly that is what happening right now). The states currently look like this:

class ActiveServerState implements WebSocketsServerState
{
    /**
     * @var int $minDelay
     */
    private $minDelay = 100;
 
    /**
     * @var int time to sleep in microseconds
     */
    private $udelay = 200000;
 
    /**
     * @param ServerStateContext $context
     * @param int $connections
     */
    public function sleep(ServerStateContext $context, $connections)
    {
        // change state to idle if there are no connections
        if ($connections < = 0) {
            $context->setState(new IdleServerState());
        } else {
            if ($connections < $this->udelay) {
                usleep(intval($this->udelay / $connections));
            } else {
                usleep($this->minDelay);
            }
        }
    }
}

As you can see, I am calling „new IdleServerState“ if there are no connections. That means, everytime there are no connections I am creating a new object.

class IdleServerState implements WebSocketsServerState
{
    /**
     * @var int time to sleep in seconds when deep sleep is entered
     */
    private $deepSleep = 5;
 
    /**
     * @var int time to sleep in seconds
     */
    private $sleep = 1;
 
    /**
     * @var int max cycles before entering deep sleep
     */
    private $maxCycles = 30;
 
    /**
     * @var int cycles
     */
    private $cycle = 0;
 
    /**
     * @param ServerStateContext $context
     * @param int $connections
     */
    public function sleep(ServerStateContext $context, $connections)
    {
        // change state to active if there are connections
        if ($connections > 0) {
            $this->cycle = 0;
            $context->setState(new ActiveServerState());
        } else {
            $delay = $this->sleep;
            if ($this->cycle > $this->maxCycles) {
                $delay = $this->deepSleep;
            }
            sleep($delay);
            $this->cycle++;
        }
    }
}

Same here. As soon as there are connections I am initiating ActiveServerState and hence I am creating a new object. Singleton to the rescue:

final class ActiveServerState implements WebSocketsServerState
{
    public static $instance = null;
 
    /**
     * @var int $minDelay
     */
    private $minDelay = 100;
 
    /**
     * @var int time to sleep in microseconds
     */
    private $udelay = 200000;
 
    private function __construct()
    {
    }
 
    private function __clone()
    {
    }
 
    public static function getInstance()
    {
        if (is_null(self::$instance)) {
            self::$instance = new self();
        }
 
        return self::$instance;
    }
 
    /**
     * @param ServerStateContext $context
     * @param int $connections
     */
    public function sleep(ServerStateContext $context, $connections)
    {
        // change state to idle if there are no connections
        if ($connections < = 0) {
            $context->setState(IdleServerState::getInstance());
        } else {
            if ($connections < $this->udelay) {
                usleep(intval($this->udelay / $connections));
            } else {
                usleep($this->minDelay);
            }
        }
    }
}

See how I only added the final-keyword to the class, added private constructor and clone methods plus the static getInstance and instance property? Furthermore I replaced new IdleServerState() with IdleServerState::getInstance(). Thats all.

final class IdleServerState implements WebSocketsServerState
{
    public static $instance = null;
 
    /**
     * @var int time to sleep in seconds when deep sleep is entered
     */
    private $deepSleep = 5;
 
    /**
     * @var int time to sleep in seconds
     */
    private $sleep = 1;
 
    /**
     * @var int max cycles before entering deep sleep
     */
    private $maxCycles = 30;
 
    /**
     * @var int cycles
     */
    private $cycle = 0;
 
    private function __construct()
    {
    }
 
    private function __clone()
    {
    }
 
    public static function getInstance()
    {
        if (is_null(self::$instance)) {
            self::$instance = new self();
        }
 
        return self::$instance;
    }
 
    /**
     * @param ServerStateContext $context
     * @param int $connections
     */
    public function sleep(ServerStateContext $context, $connections)
    {
        // change state to active if there are connections
        if ($connections > 0) {
            $this->cycle = 0;
            $context->setState(ActiveServerState::getInstance());
        } else {
            $delay = $this->sleep;
            if ($this->cycle > $this->maxCycles) {
                $delay = $this->deepSleep;
            }
            sleep($delay);
            $this->cycle++;
        }
    }
}

In my ServerStateContext Class (which you can see in my last post) I have to replace $this->setState(new IdleServerState()); with $this->setState(IdleServerState::getInstance());

No Comments

Post a Comment