Design Patterns: Singleton

7 02 2007

Singleton é um design pattern utilizado quando se deseja que exista apenas uma
instância de uma determinada classe. Exemplos de classes que poderiam ser
utilizadas como singleton são classes de acesso a banco de dados da aplicação
(há casos em que pode não ser possível), configurações, gerenciamento de layout,
gerenciamento de cache, …

Vejamos por exemplo uma simples classe de conexão com banco de dados (não implementada)
e como implementar o design pattern:

<?php
  // implementacao da classe - sem singleton
  class Conexao {
    private $link;

    public function connect()
    {
    }

    public function fetch()
    {
    }

    public function executeQuery()
    {
    }
  }

  // em alguma parte da aplicacao, instanciamos a classe,
  // efetuamos a conexao e executamos a query
  $con = new Conexao();
  $con->connect();
  $con->executeQuery('select * from bla');
  $rs = $con->fetch();

  // ----------------------------

  // em alguma outra parte da aplicacao, nao necessariamente no
  // mesmo arquivo, seria necessario ou adivinhar o nome da variavel
  // que foi utilizada na conexao para reutiliza-la, talvez utilizando
  // global (pessima ideia), ou efetuar uma nova conexao (ideia ainda pior)
  function foo()
  {
    global $con;
    $con->executeQuery('select * from bla');
    $rs = $con->fetch();
  }

  function bar()
  {
    $con = new Conexao();
    $con->connect();
    $con->executeQuery('select * from bla');
    $rs = $con->fetch();
  }

  foo();
  bar();
?>

Neste caso, o seguinte acontece:
– Uma conexão é criada
– Uma query é executada
– É efetuada uma chamada para a função foo(), que utiliza global para poder fazer uso da
conexão com o banco de dados criada anteriormente e então executar uma query
– É efetuada uma chamada para a função bar(), que cria uma nova conexão com o banco de
dados para executar uma query

Implementado o singleton na classe de conexão:

<?php
  // implementacao da classe - sem singleton
  class Conexao {
    // propriedade que ira conter a instancia da classe
    private static $instance = null;

    private $link;

    // construtor privado. Para se obter a instancia da classe
    // e' necessario uma chamada ao metodo getInstance()
    private function __construct()
    {
    }

    // metodo static, que ira verificar se a classe ja foi instanciada
    // ou nao. caso nao tenha sido, ira criar a instancia, efetuar a conexao
    // e retornar a instancia. caso ja tenha sido criada a instancia, apenas a
    // retorna
    public static function getInstance()
    {
      if ( self::$instance === null ) {
        self::$instance = new Conexao();
        self::$instance->connect();
      }
      return self::$instance;
    }

    private function connect()
    {
    }

    public function fetch()
    {
    }

    public function executeQuery()
    {
    }
  }

  // Agora apenas obtemos a instancia e executamos a query
  $con = Conexao::getInstance();
  $con->executeQuery('select * from bla');
  $rs = $con->fetch();

  // ----------------------------

  // agora tanto a funcao foo() quanto a funcao bar() executam queries
  // da mesma forma, obtendo a instancia e entao executando a query.
  //
  // Assim nao existe criacao de uma nova conexao, que pode fazer com que
  // uma aplicacao se torne indisponivel rapidamente, e tambem nao ha o problema
  // de se ter uma variavel global, acessivel pela aplicacao inteira e que pode
  // eventuamente ter o conteudo "apagado" entre uma chamada e outra

  function foo()
  {
    $con = Conexao::getInstance();;
    $con->executeQuery('select * from bla');
    $rs = $con->fetch();
  }

  function bar()
  {
    $con = Conexao::getInstance();;
    $con->executeQuery('select * from bla');
    $rs = $con->fetch();
  }

  foo();
  bar();
?>

Analisando o comportamento da classe singleton, vemos que realmente existirá apenas
uma instância por toda a aplicação:

<?php
  // implementacao da classe - sem singleton
  class Conexao {
    // propriedade que ira conter a instancia da classe
    private static $instance = null;

    private $link;

    // construtor privado. Para se obter a instancia da classe
    // e' necessario uma chamada ao metodo getInstance()
    private function __construct()
    {
    }

    // metodo static, que ira verificar se a classe ja foi instanciada
    // ou nao. caso nao tenha sido, ira criar a instancia, efetuar a conexao
    // e retornar a instancia. caso ja tenha sido criada a instancia, apenas a
    // retorna
    public static function getInstance()
    {
      if ( self::$instance === null ) {
        self::$instance = new Conexao();
        self::$instance->connect();
      }
      return self::$instance;
    }

    private function connect()
    {
    }

    public function fetch()
    {
    }

    public function executeQuery()
    {
    }
  }

  // obtem-se uma instancia
  $instancia1 = Conexao::getInstance();

  // obtem-se a instancia mais uma vez
  $instancia2 = Conexao::getInstance();

  // exibe a representacao das duas instancias
  echo 'Instancia 1: ';
  var_dump ( $instancia1 );

  echo 'Instancia 2: ';
  var_dump ( $instancia2 );

  // verifica se as duas variaveis sao exatamente iguais.
  // note que duas instancias de uma mesma classe nao sao
  // exatamente iguais
  echo 'Exatamente iguais: ';
  var_dump ( $instancia1 === $instancia2 );
?>

$ php conexao.php
Instancia 1: object(Conexao)#1 (1) {
[“link:private”]=>
NULL
}
Instancia 2: object(Conexao)#1 (1) {
[“link:private”]=>
NULL
}
Exatamente iguais: bool(true)

É observável o funcionamento de uma classe singleton. Mas ainda há um detalhe:
Quando se é desejavel absoluta certeza que não existirá mesmo uma segunda instância
da classe, é necessário estar atento que, da forma que esta, a instância ainda pode
ser “clonada”, criando uma segunda instância. Observe:

<?php
  // implementacao da classe - sem singleton
  class Conexao {
    // propriedade que ira conter a instancia da classe
    private static $instance = null;

    private $link;

    // construtor privado. Para se obter a instancia da classe
    // e' necessario uma chamada ao metodo getInstance()
    private function __construct()
    {
    }

    // metodo static, que ira verificar se a classe ja foi instanciada
    // ou nao. caso nao tenha sido, ira criar a instancia, efetuar a conexao
    // e retornar a instancia. caso ja tenha sido criada a instancia, apenas a
    // retorna
    public static function getInstance()
    {
      if ( self::$instance === null ) {
        self::$instance = new Conexao();
        self::$instance->connect();
      }
      return self::$instance;
    }

    private function connect()
    {
    }

    public function fetch()
    {
    }

    public function executeQuery()
    {
    }
  }

  // obtem-se uma instancia
  $instancia1 = Conexao::getInstance();

  // cria um clone da instancia

  $instancia2 = clone($instancia1);

  // exibe a representacao das duas instancias
  echo 'Instancia 1: ';
  var_dump ( $instancia1 );

  echo 'Instancia 2: ';
  var_dump ( $instancia2 );

  // verifica se as duas variaveis sao exatamente iguais.
  // note que duas instancias de uma mesma classe nao sao
  // exatamente iguais
  echo 'Exatamente iguais: ';
  var_dump ( $instancia1 === $instancia2 );
?>

$ php conexao.php
Instancia 1: object(Conexao)#1 (1) {
[“link:private”]=>
NULL
}
Instancia 2: object(Conexao)#2 (1) {
[“link:private”]=>
NULL
}
Exatamente iguais: bool(false)

Observe que as instâncias não são mais as mesmas – object(Conexao)#1 e object(Conexao)#2
e também não são mais exatamente iguais.

Para contornar esse problema, podemos implementar o método __clone na classe Conexao.
O método clone é chamado no momento em que a instância da classe em questão é clonada.
Vejamos:

<?php
  // implementacao da classe - sem singleton
  class Conexao {
    // propriedade que ira conter a instancia da classe
    private static $instance = null;

    private $link;

    // construtor privado. Para se obter a instancia da classe
    // e' necessario uma chamada ao metodo getInstance()
    private function __construct()
    {
    }

    // metodo static, que ira verificar se a classe ja foi instanciada
    // ou nao. caso nao tenha sido, ira criar a instancia, efetuar a conexao
    // e retornar a instancia. caso ja tenha sido criada a instancia, apenas a
    // retorna
    public static function getInstance()
    {
      if ( self::$instance === null ) {
        self::$instance = new Conexao();
        self::$instance->connect();
      }
      return self::$instance;
    }

    // implementacao do metodo clone, chamado no momento em que uma instancia
    // e' clonada
    public function __clone()
    {
      throw new Exception('impossivel clonar');
    }

    private function connect()
    {
    }

    public function fetch()
    {
    }

    public function executeQuery()
    {
    }
  }

  // obtem-se uma instancia
  $instancia1 = Conexao::getInstance();

  // cria um clone da instancia

  $instancia2 = clone($instancia1);

  // exibe a representacao das duas instancias
  echo 'Instancia 1: ';
  var_dump ( $instancia1 );

  echo 'Instancia 2: ';
  var_dump ( $instancia2 );

  // verifica se as duas variaveis sao exatamente iguais.
  // note que duas instancias de uma mesma classe nao sao
  // exatamente iguais
  echo 'Exatamente iguais: ';
  var_dump ( $instancia1 === $instancia2 );
?>

$ php conexao.php
PHP Fatal error: Uncaught exception ‘Exception’ with message ‘impossivel clonar’ in conexao.php:32
Stack trace:
#0 conexao.php(53): Conexao->__clone()
#1 {main}
thrown in conexao.php on line 32

Fatal error: Uncaught exception ‘Exception’ with message ‘impossivel clonar’ in conexao.php:32
Stack trace:
#0 conexao.php(53): Conexao->__clone()
#1 {main}
thrown in conexao.php on line 32

E desta forma é não será possível ter duas instâncias da mesma classe.

Anúncios

Ações

Information

One response

20 03 2009
Gabriel

Muito bom cara, valeu!

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s




%d blogueiros gostam disto: