Ir ao conteúdo

Padrão de Projeto Singleton em PHP com exemplo

Atualizado pela última vez em 16 de novembro de 2021

O Padrão Singleton é utilizado quando queremos que exista só uma única instância de uma classe e que ela possa ser facilmente acessível de qualquer ponto do programa. É uma boa alternativa às variáveis globais e as classes que só possuem métodos estáticos. Em um Singleton, a própria classe é responsável por gerenciar sua única instância.

A representação do Padrão de Projeto Singleton utilizando o diagrama de classes da UML é bastante simples e intuitivo no seu entendimento.

Exemplos práticos utilizando o padrão de projeto Singleton

Os padrões de projetos são utilizados na sua grande maioria das vezes em projetos que utilizam linguagens de programação orientadas a objetos. Então, alguns aspectos do paradigma de Programação Orientação a Objetos são imprescindíveis.

Este padrão é bem simples de ser utilizado, porém você precisa ter atenção a alguns aspectos de qualidade de código. No código a seguir, utilizamos um método lazy, que se encarregará de garantir que essa classe só tenha uma única instância apontando para um ponto específico da memória, de modo que ao chamarmos esta instância mais de uma vez, você estará literalmente acessando uma instância única, que é o propósito deste padrão de projeto.

<?php

declare(strict_types=1);

namespace Growthdev\DesignPatterns\Creational\Singleton;

class Singleton
{
    private static ?Singleton $instance = null;

    public static function getInstance(): Singleton
    {
        if (null === static::$instance) {
            static::$instance = new self();
        }

        return static::$instance;
    }
}

Quais problemas temos ao utilizar o Padrão Singleton?

Muitas linguagens dinâmicas como o PHP, permitem a serialização e deserialização de classes, com isso você “burlaria” esse  recurso do Singleton, além do recurso de clonagem de objetos. Para resolver este problema de garantir a instância única, você deve privar os métodos que estão intimamente ligados a estes recursos ou, como no PHP 8 alguns métodos mágicos não permitem a alteração de visibilidade, você define disparadores de exceção como mostrado a seguir:

<?php

declare(strict_types=1);

namespace Growthdev\DesignPatterns\Creational\Singleton;

use LogicException;

class Singleton
{
    private static ?Singleton $instance = null;

    public static function getInstance(): Singleton
    {
        if (null === static::$instance) {
            static::$instance = new self();
        }

        return static::$instance;
    }

    private function __construct()
    {
    }

    private function __clone(): void
    {
    }
    
    public function __sleep(): array
    {
        throw new LogicException('Cannot serialize a singleton.');
        return [];
    }
    
    public function __wakeup(): void
    {
        throw new LogicException('Cannot unserialize a singleton.');
    }
}

Para facilitar um pouco mais o seu entendimento, fiz alguns testes para exemplificar o comportamento que esperamos com o uso do deste pattern.

<?php

declare(strict_types=1);

namespace Growthdev\DesignPatterns\Creational\Singleton;

use Growthdev\DesignPatterns\Creational\Singleton\Singleton;
use Error;
use LogicException;
use PHPUnit\Framework\TestCase;

final class SingletonTest extends TestCase
{
    public function testShouldBeTheSameInstanceForTwoObjets(): void
    {
        $firstInstance = Singleton::getInstance();
        $secondInstance = SingleTon::getInstance();

        $this->assertSame($firstInstance, $secondInstance);
    }

    public function testShouldThrowErrorWhenTryToCreateInstance(): void
    {
        $this->expectException(Error::class);

        $instance = new Singleton();
    }

    public function testShouldThrowErrorWhenTryToClone(): void
    {
        $this->expectException(Error::class);

        $instance = Singleton::getInstance();
        $clone = clone $instance;
    }

    public function testShouldThrowExceptionWhenTryToSerialize(): void
    {
        $this->expectException(LogicException::class);
        $this->expectExceptionMessage('Cannot serialize a singleton.');

        $instance = Singleton::getInstance();
        $serialize = serialize($instance);
    }

    public function testShouldThrowExceptionWhenTryToUnserialize(): void
    {
        $this->expectException(LogicException::class);
        //$this->expectExceptionMessage('Cannot unserialize a singleton.');

        $instance = Singleton::getInstance();
        $serialize = serialize($instance);  // vai cair na exceção do serialize

        $unserialize = unserialize($serialize); // não vai chegar aqui
    }
}

Resultado da saída dos testes:

Singleton - Resultado teste uniário

Obs.: Todos os códigos contidos neste artigo disponibilizei no meu repositório no Gitthub:

https://github.com/growthdev-repo/design-patterns

Como tudo na vida, “Nem tudo são flores”… Você vai encontrar algumas barreiras nos testes. Tem uma galera mais radical que define o Singleton como anti-pattern, pelo fato dele acoplar o seu código em uma implementação estática e específica. 

Outro ponto importante a se observar, é que temos à nossa disposição, dezenas de outros recursos super interessantes que nos auxiliam em arquitetar um sistema , como é o caso da Injeção de Dependências para evitar acoplamentos. Tente injetar um Singleton e veja o que ocorre. Fez o teste? Deixa o seu comentário aqui falando sobre o que aconteceu.

Quais as vantagens do Padrão de Projeto Singleton?

A resposta pode parecer óbvia, mas esse padrão é útil quando queremos garantir um ponto único para criação de instância de classe e garantir que haverá apenas uma única instância. Um uso muito comum deste padrão é, por exemplo, na criação de classes de conexões e de controle de Logs.

Como tudo na vida tem vantagens e desvantagens, se os pontos negativos que citei relativos ao uso do Singleton, não forem impactar o design de sua aplicação drasticamente, acredito que “demonizar” este pattern é um exagero. 

Tem alternativas que podem ser utilizadas para conseguir um comportamento similar, como é o caso do padrão Monostate. Sua estratégia é utilizar propriedades estáticas como ponto único e deixar as classes livres para serem “instanciadas” ou “injetadas” livres e felizes.

Se você tem algum ponto que pode contribuir com este artigo. Fique livre para deixar seu comentário. Se gostou, compartilhe com sua rede, isso vai ser um grande diferencial para sua carreira profissional. Mostrará que você está consumindo conteúdo técnicos que agregam ao seu crescimento. Até o próximo artigo!

Confiança Sempre!!!

Fontes:

Publicado emDesign PatternPadrões de ProjetosPHPProgramação

Seja o primeiro a comentar

    Deixe um comentário

    O seu endereço de e-mail não será publicado.