Se você leu o arquivo de código fonte Singleton.h, deve ter percebido cinco novas classes: LockerException, Locker, DummyLocker, SynchroTypleton e SynchroTempleton. E a adição da função membro estática synchroInitialize na classe SingletonController.
Por que eu fiz dessa forma? Por que copiar e modificar levemente classes existentes se eu posso usar herança para isso? A resposta é simples: esse é um daqueles casos nos quais é muito mais fácil copiar e colar do que aplicar o paradigma ou a receita de programação presente na ordem do dia. Também era preciso garantir que as contrapartidas sincronizadas de Templeton e Typleton estão isoladas dessas classes não sincronizadas. Nascemos para ser espertos, e livres mesmo para usar padrões ruins - mas de forma inteligente. Nós não nascemos para sermos os mais elegantes e rigorosos designers do mundo. Não somos escravos dos patterns: somos seus mestres.
Vamos entender o papel de cada nova classe usada para prover Singletons sincronizados. A primeira classe que precisamos entender é LockerException. Essa classe representa uma exceção de bloqueio (lock) de processamento. Programar em ambientes multitarefa é lidar com esse tipo de exceção durante todo o tempo. Então Featherns oferece uma classe padrão para essas exceções.
As próximas classes a serem compreendidas são Locker e DummyLocker. Os Singletons sincronizados (SynchroTypleton e SynchroTempleton), para serem sincronizados, precisam receber uma classe de sincronização que implemente a interface definida por Locker. Como você pode ver, Locker é uma classe com duas funções membro virtuais puras e elas definem o modo que os Singletons sincronizados restringem o acesso ao seu conteúdo. Se Locker é uma interface, DummyLocker é a sua implementação padrão. E ela é burra. DummyLocker não retringe acesso de nada. Ela é apenas uma implementação cuja função é ser ignorada. Criar um Singleton sincronizado sem definir um Locker personalizado é criar um Singleton não sincronizado.
Antes de explicar os Singletons sincronizados, eu lhes darei uma justificativa para a adição de uma nova função em SingletonController. Não era impossível, para nós, usar a mesma função initialize, presente em SingletonController, em SynchroTempleton. Mas, se fizéssemos isso, Templeton e SynchroTempleton seriam o mesmo Singleton, isto é, se alguém instanciasse um Templeton, SynchroTempleton não poderia ser instanciado, e vice-versa. Então criei uma nova função para ser usada apenas por SynchroTempleton, isolando-o completamente de Templeton.
Agora podemos seguir para os Singletons Sincronizados. Se olharmos para o código dos Singletons Sincronizados, SynchroTypleton (localizado na linha 114 de Singleton.h) e SychroTempleton (localizado na linha 230 de Singleton.h), a maior diferença notada entre eles e suas contrapartes não sincronizadas não é a infra-estrutura de sincronização que eles fornecem, mas a sua natureza como contêineres. SynchroTypleton e SynchroTempleton não mais contêm ponteiros.
Eles contêm, agora, cópias completas de objetos. Esse completo distanciamento do seu conteúdo em relação à sua antiga natureza como ponteiros tornou inviável a aplicação de qualquer tipo de herança entre os Singletons Sincronizados e os seus respectivos irmãos não sincronizados. Mas essa mudança era necessária, uma vez que a sincronização só é efetiva se estamos lidando com instâncias reais de objetos, não com seus ponteiros. Os Singletons Sincronizados apenas liberam cópias dos seus objetos conteúdo para o usuário, e, também, apenas permitem substituição sincronizada desses objetos pelos usuários.
Usar objetos no lugar de ponteiros como conteúdo nos Singletons Sincronizados nos dá as mais completas capacidades de sincronização e uma grande vantagem: nós não precisamos mais lidar com indireção de ponteiros. Isso torna o código fonte mais fácil de ler e muito mais prazeroso de ser escrito. Se você analisar os exemplos que eu vou lhes fornecer, mais tarde, verão um código mais amigável quando usamos os Singletons Sincronizados.
Mas, por outro lado, nós temos muito mais prejuízos do que benefícios: menor eficiência, uma vez que lidar com cópias de objetos precisa de muito mais processamento do que lidar com ponteiros, e restrições na definição de que objetos podem ser armazenados no contêiner. Se um contêiner armazena ponteiros, então qualquer tipo pode ser armazenado pelo contêiner.
Mas se o contêiner armazena objetos, esse objeto precisa ser de um tipo primitivo (como int, char, or um ponteiro) ou ele precisa ser uma instância de uma classe bem comportada, que implemente um constructor padrão, sem parâmetros, que implemente um constructor de cópia ou, algumas vezes, que sobreponha o operador de atribuição. Mas esse é o custo de uma infra-estrutura de sincronização, e o programador precisa pensar duas vezes antes de usar um Singleton Sincronizado no lugar de um não sincronizado.
Agora nós alcançamos o ponto realmente importante. Os Singletons Sincronizados não fornecem nenhum tipo de sincronização. Eles fornecem uma infra-estrutura. Quem fornece o código de sincronização é o usuário. Esse código de sincronização pode ser implementado usando o Boost, POSIX Threads ou CriticalSection objects da API Win32. Featherns não poderia fornecer nenhum tipo de sincronização porque esse tipo de código é dependente de plataforma.
Então ele conta com alguma implementação da classe Locker fornecida pelo usuário. Featherns depende de uma classe externa para sincronizar os seus Singletons. Você pode ver nos exemplos como implementar essas classes externas. Mas as classes no exemplo (SynchroTempleton.cpp), VerboseDummyLocker e UpperVerboseDummyLocker são apenas exemplos, e não fornecem nenhuma sincronização, apenas apresentam como elas devem ser chamadas por SynchroTypleton e SynchroTempleton. Esse tipo de dependência em recursos externos por um programa, biblioteca ou função é o que nós chamados de
Injeção de Dependência.
O usuário, para usar os Singletons Sincronizados corretamente, precisa implementar um Locker e injetá-lo na classe de Singleton da sua escolha. Essa injeção é feita em tempo de compilação através de uma instanciação de template. Daí o nome do pattern: Injeção de Dependência. Mas Injeção de Dependência é muito mais do que isso, e, no próximo artigo, iremos discuti-la melhor. Então chegaremos até a Inversão de Controle.
Depois de todas essas elucidações, vamos para os exemplos. Você pode pegar os códigos de exemplo clicando
nesse link. Depois disso, tenha a certeza de que você possui a última versão do CMake (CMake 2.8.2) rodando no seu sistema. CMake pode vir em algum pacote já incluso na sua Distribuição de Linux e usuários de Mac OS X podem usar o MacPorts para consegui-lo. Usuários de FreeBSD podem usar o Ports do sistema. Mas, para sistemas com versões mais antigas de CMake nos seus pacotes (como Debian Lenny) ou para usuários de Windows, vocês podem obter o CMake no seu site [1]. Eu não darei instruções de compilação para sistemas Windows (incluindo MinGW ou Cygwin), mas, para compliar e rodar os exemplos, em qualquer sistema Unix (incluindo o Mac OS X, talvez o Cygwin), apenas baixe o arquivo e proceda como o descrito abaixo:
tar -jxvf feathernscpatt-0.0.1.tar.bz2
$ mkdir BUILD
$ cd BUILD
$ cmake ../feathernscpatt/samples/
$ make
$ cd bin
Agora, com os exemplos rodando, comece a explorar a árvore de arquivos de Featherns, no diretório feathernscpatt. Você encontrará o diretório de inclusão de arquivos de cabeçalho de Featherns. Apenas adicione esse diretório ao include path de seu programa para usar os Singletons. Ou apenas copie o arquivo Singleton.h, para qualquer lugar que você quiser, para usá-lo em seu projeto. Divirta-se! Até a próxima!
Créditos:
[1] O CMake pode ser baixado nesse endereço:
http://www.cmake.org/cmake/resources/software.html