Padrão Singleton
Padrões e Frameworks
Roberto A. Bittencourt Jairo Calmon
Singleton
• Como a maioria dos programadores organizaria o código para acessar informação de configuração? • Eis um exemplo
public class Config {
public static final String DEFAULT_READ_COMMUNITY_NAME = "public"; public static final String DEFAULT_CONCENTRATOR_RETRIES = "3"; public static final String DEFAULT_CONCENTRATOR_TIMEOUT = "3000";
public static final String DEFAULT_URL_CONSUMER_DATABASE = "jdbc:DBF:////home/jacques/src/celb-database";
}
public class Foo{
// ... facaAlgo(Config.DEFAULT_READ_COMMUNITY_NAME); // ...
Objetivo
• Assegurar que uma classe tenha uma única
instância e prover um ponto de acesso global
a esta instância
Motivação
• Algumas classes devem ser instanciadas uma única vez:
– Um spooler de impressão – Um sistema de arquivos – Um Window manager
– Um objeto que contém a configuração de um programa – etc.
• Como assegurar que uma classe possua apenas uma instância e que esta instância seja facilmente acessível?
– Uma variável global deixa a instância acessível mas não inibe a instanciação múltipla
Motivação
• Uma melhor solução: faça com que a classe em si seja responsável pela manutenção da instância única • Este é o padrão Singleton ("que-possui-apenas-um")
E este também é um dos argumentos para dizer que é um anti-pattern.
Aplicabilidade
• Use o padrão Singleton quando:
– Deve haver uma única instância de uma classe e esta instância deve ser acessada a partir de um ponto de acesso bem-conhecido
– Quando a instância única deve ser extensível através de subclasses e clientes podem usar instâncias diferentes polimorficamente, sem modificação de código
Motivação
Mais um motivo para considerar como sendo um anti-pattern:
– “Uma única instância de uma classe que deve ser acessada a partir de um ponto bem conhecido”
– Para alguns é só uma maneira mais bonita de dizer:
• VARIÁVEL GLOBAAAL!!! EBAAAA!!! HUEHUEHUEBR!!111
– Por isso seu uso deve ser bem avaliado
Participantes
• Singleton
– Define uma operação getInstance() que permite que clientes acessem sua instância única
• É um método estático (class method)
– Pode ser responsável pela criação de sua instância única
Colaborações
• Clientes acessam a instância apenas através da
operação getInstance() do Singleton
Consequências
• Vários benefícios existem:
– Acesso controlado à instância única
• O singleton tem controle sobre como e quando clientes acessam a instância
– Espaço de nomes reduzido
• O Singleton é melhor que variáveis globais, já que as "globais" podem ser encapsuladas na instância única, deixando um único nome externo visível
– Permite refinamento de operações e de representação
• Várias classes Singleton (relacionadas ou não via herança) podem obedecer a mesma interface, permitindo que um singleton
particular seja escolhido para trabalhar com uma determinada aplicação em tempo de execução
Consequências
– Permite a existência de um número variável de instâncias • É fácil fazer com que o Singleton crie um número fixo, ou um
número máximo de instâncias em vez de apenas uma única instância
• Apenas a implementação interna do Singleton precisa mudar – Mais flexível que métodos estáticos
• Embora possa parecer que podemos fazer o equivalente a um
Singleton com métodos estáticos, lembre que isso não permitiria o polimorfismo:
NomeDeClasse.foo(); // chamada não é polimórfica // ... versus ...
Considerações de implementação
• Como assegurar que haja uma única instância?
• Uma forma é de não permitir chamadas ao construtor
• Observe que o uso de lazy instantiation é preferível a retornar uma instância estática da classe
– Lazy instantiation pode ser mais rápida se a instanciação/inicialização for lenta
public class Config implements ConfigIF {
private static ConfigIF instânciaÚnica = null;
private Config() {} // o compilador não vai gerar um construtor default público
public static ConfigIF getInstance() { if(instânciaÚnica == null) {
// "lazy instantiation"
instânciaÚnica = new Config(); }
return instânciaÚnica; }
Anti-pattern?
• Outros problemas que levam alguns a considerarem Singleton como anti-pattern:
– Não é Test-friendly. É difícil realizar testes com o singleton sem ter que reiniciar o estado
– Problemas de sincronização.
• Implementações geralmente defeituosas ou ineficientes
– Uso indiscriminado (muitos usam apenas a “facilidade” de ter uma variável global no código.
– “it is overused, introduces unnecessary restrictions in situations where a sole instance of a class is not actually required, and introduces global state into an application”
• Ver links disponíveis em
http://dsc.ufcg.edu.br/~jacques/cursos/map/html/pat/singleton.htm
Padrão Abstract Factory
Padrões e Frameworks
Roberto A. Bittencourt Jairo Calmon
Relembrando o Factory Method
• Na aula do Factory Method, vimos o exemplo do
labirinto.
– Tínhamos uma classe Jogo, com métodos de criação e um método montaLabirinto()
– Tínhamos subclasses de Jogo (ex.: JogoPerigoso), que sobreescrevia os métodos de criação, mas não o
método montaLabirinto().
– Com isso a estrutura do labirinto permanecia
idêntica, mas os elementos criados pelos métodos de criação eram modificados.
Classe Jogo
public class Jogo {
// Factory Methods com default
public Labirinto criaLabirinto() { return new Labirinto();
}
public Sala criaSala(int númeroDaSala) { return new Sala(númeroDaSala);
}
public Parede criaParede() { return new Parede();
}
public Porta criaPorta(Sala sala1, Sala sala2) { return new Porta(sala1, sala2);
} ...
Classe Jogo
...
public Labirinto montaLabirinto() {
Labirinto umLabirinto = criaLabirinto(); Sala sala1 = criaSala(1);
Sala sala2 = criaSala(2);
Porta aporta = criaPorta(sala1, sala2); sala1.setVizinho(NORTE, criaParede()); sala1.setVizinho(LESTE, aporta); sala1.setVizinho(SUL, criaParede()); sala1.setVizinho(OESTE, criaParede()); sala2.setVizinho(NORTE, criaParede()); sala2.setVizinho(LESTE, criaParede()); sala2.setVizinho(SUL, criaParede()); sala2.setVizinho(OESTE, aporta); return umLabirinto; }
Classe JogoPerigoso
public class JogoPerigoso extends Jogo { public Parede criaParede() {
return new ParedeDestrutível(); }
public Sala criaSala(int númeroDaSala) { return new SalaComBomba(númeroDaSala); }
Abstract Factory
• Mas e se fizéssemos de outra forma?
• E se separássemos mais um pouco a função de
criar objetos?
Classe Jogo
public Labirinto montaLabirinto() {
Labirinto umLabirinto = criaLabirinto(); Sala sala1 = criaSala(1);
Sala sala2 = criaSala(2);
Porta aporta = criaPorta(sala1, sala2); sala1.setVizinho(NORTE, criaParede()); sala1.setVizinho(LESTE, aporta); sala1.setVizinho(SUL, criaParede()); sala1.setVizinho(OESTE, criaParede()); sala2.setVizinho(NORTE, criaParede()); sala2.setVizinho(LESTE, criaParede()); sala2.setVizinho(SUL, criaParede()); sala2.setVizinho(OESTE, aporta); return umLabirinto; }
Classe Jogo (Abstract Factory)
public LabirintoIF montaLabirinto(FactoryDeLabirintoIF factory) { LabirintoIF umLabirinto = factory.criaLabirinto();
SalaIF sala1 = factory.criaSala(1); SalaIF sala2 = factory.criaSala(2);
PortaIF aPorta = factory.criaPorta(sala1, sala2); sala1.setVizinho(NORTE, factory.criaParede()); sala1.setVizinho(LESTE, aPorta);
sala1.setVizinho(SUL, factory.criaParede()); sala1.setVizinho(OESTE, factory.criaParede()); sala2.setVizinho(NORTE, factory.criaParede()); sala2.setVizinho(LESTE, factory.criaParede()); sala2.setVizinho(SUL, factory.criaParede()); sala2.setVizinho(OESTE, aPorta);
return umLabirinto; }
Abstract Factory
Abstract Factory
• Padrão semelhante ao Factory Method, mas
– Em vez do cliente (que quer criar objetos sem saber as classes exatas) chamar um método de criação (Factory Method), ele de alguma forma possui um objeto (uma Abstract Factory) e usa este objeto para chamar os
métodos de criação
– Onde Factory Method quer que você seja diferente (via herança) para criar objetos diferentes, o Abstract Factory quer que você tenha algo diferente
Abstract Factory
• Se ele possuir uma referência a uma Abstract
Factory diferente, toda a criação será
diferente
• O fato de todos os métodos de criação
estarem na mesma subclasse de uma Abstract
Factory permite satisfazer a restrição de criar
apenas objetos relacionados ou dependentes
Abstract Factory
• Ou seja... ao invés da classe “Jogo” ter que ser
estendida para implementar os factory
methods, eu apenas passo uma “Factory”.
• E aí? Interessante?
• Note que a partir de agora, as factories que
vão ser implementadas / estendidas.
Classe FactoryDeLabirinto
public class FactoryDeLabirinto implements FactoryDeLabirintoIF { // Factory Methods
// Tem default para as Factory Methods public LabirintoIF criaLabirinto() { return new Labirinto();
}
public SalaIF criaSala(int númeroDaSala) { return new Sala(númeroDaSala);
}
public ParedeIF criaParede() { return new Parede();
}
public PortaIF criaPorta(SalaIF sala1, SalaIF sala2) { return new Porta(sala1, sala2);
Classe FactoryDeLabirintoPerigoso
public class FactoryDeLabirintoPerigoso extends FactoryDeLabirinto { public SalaIF criaSala(int númeroDaSala) {
return new SalaComBomba(númeroDaSala); }
public ParedeIF criaParede() { return new ParedeDestrutível(); }
}
JogoIF umJogo = new Jogo();
FactoryDeLabirinto factory = new FactoryDeLabirintoPerigoso(); jogo.montaLabirinto(factory);
Abstract Factory
• Observe que podemos ir um pouco além.
• Podemos introduzir o padrão Singleton, bem
como deixar a classe FactoryDeLabirinto
responsável por instanciar as factories.
– Tem como vantagem tirar do usuário a utilização das subclasses, uma vez que ele está interessado apenas na interface.
Classe FactoryDeLabirintoPerigoso
public class FactoryDeLabirinto implements FactoryDeLabirintoIF { private static FactoryDeLabirintoIF instânciaÚnica = null;
private FactoryDeLabirinto() {}
public static FactoryDeLabirintoIF getInstance(String tipo) { if(instânciaÚnica == null) {
if(tipo.equals("perigoso")) {
instânciaÚnica = new FactoryDeLabirintoPerigoso(); } else if(tipo.equals("encantado")) {
instânciaÚnica = new FactoryDeLabirintoEncantado(); } else {
instânciaÚnica = new FactoryDeLabirinto(); }
}
return instânciaÚnica; }
Abstract Factory
• Ao invés do usuário instanciar conhecendo as
subclasses ele passa a criar assim:
JogoIF umJogo = new Jogo();
FactoryDeLabirinto factory = new FactoryDeLabirinto(“perigoso”);
Abstract Factory
• Objetivo
– Prover uma interface para criar uma família de objetos relacionados ou dependentes sem
especificar suas classes concretas
• Também chamado de
Exemplo: look-and-feel de GUIs
• Para look-and-feel diferentes (Motif, Windows, Mac,
Presentation Manager, etc.) temos formas diferentes de manipular janelas, scroll bars, menus, etc.
• Para criar uma aplicação com GUI que suporte qualquer look-and-feel, precisamos ter uma forma simples de criar objetos (relacionados) de uma mesma família
• Os objetos são dependentes porque não posso criar uma janela estilo Windows e um menu estilo Motif
• Java já resolveu este problema internamente no package awt usando Abstract Factory e você não precisa se preocupar com isso
– Porém, você poderia estar usando C++ e precisaria cuidar disso você mesmo
Exemplo: look-and-feel de GUIs
• Uma classe (abstrata) (ou interface, em Java)
"Abstract Factory" define uma interface para criar cada tipo de objeto básico (widgets no linguajar GUI) • Também tem uma classe abstrata para cada tipo de
widget (window, scroll bar, menu, ...)
• Há classes concretas para implementar cada widget em cada plataforma (look-and-feel)
• Clientes chamam a Abstract Factory para criar objetos
Exemplo: look-and-feel de GUIs
• Perceba o que o cliente precisa conhecer apenas:
– A interface da fábrica (WidgetFactoryIF)
– A interface de cada widget (WindowIF, ScrollBarIF)
• Em Java costumamos fazer isso:
public static void main(String args[]) {
try {
UIManager.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel"); } catch (Exception e) {
System.out.println("Nimbus isn't available"); }
TelaVaquinha vaquinhaGUI = new TelaVaquinha(); vaquinhaGUI.setVisible(true);
O padrão Abstract Factory
• Quando usar o padrão Abstract Factory?
– Quando um sistema deve ser independente de como seus produtos são criados, compostos e representados
– Quando um sistema deve ser configurado com uma entre várias famílias de produtos
– Quando uma família de produtos relacionados foi
projetada para uso conjunto e você deve implementar essa restrição
– Quando você quer fornecer uma biblioteca de classes e quer revelar sua interface e não sua implementação
• Não permita portanto que objetos sejam diretamente criados com new
Participantes
• FactoryIF (WidgetFactoryIF)
– Define uma interface para as operações que criam objetos como produtos abstratos
• FactoryAbstrata (WidgetFactory)
– Possível classe abstrata para fatorar o código comum às FactoryConcretas
• FactoryConcreta (MotifWidgetFactory, WindowsWidgetFactory)
– Implementa as operações para criar objetos para produtos concretos
• ProdutoXIF (WindowIF, ScrollBarIF)
– Define uma interface para objetos de um tipo
• ProdutoAbstrato (Window, ScrollBar)
– Possível classe abstrata para fatorar o código comum aos ProdutosConcretos
• ProdutoConcreto (MotifWindow, MotifScrollBar)
– Define um objeto produto a ser criado pela FactoryConcreta correspondente – Implementa a interface de ProdutoAbstrato
• Cliente
Colaborações entre objetos
• Normalmente uma única instância de uma classe FactoryConcreta é criada em tempo de execução • Essa FactoryConcreta cria objetos tendo uma
implementação particular
• Para criar produtos diferentes, clientes devem usar uma FactoryConcreta diferente
• FactoryAbstrata depende de suas subclasses FactoryConcreta para criar objetos de produtos
Consequências do uso do padrão Abstract Factory
• O padrão isola classes concretas
– Uma factory encapsula a responsabilidade e o processo de criação de objetos de produtos
– Isola clientes das classes de implementação
– O cliente manipula instâncias através de suas interfaces abstratas
Consequências do uso do padrão Abstract Factory
• Facilita o câmbio de famílias de produtos
– A classe da FactoryConcreta só aparece em um lugar: onde ela é instanciada
– Uma mudança numa única linha de código pode ser
suficiente para mudar a FactoryConcreta que a aplicação usa
Consequências do uso do padrão Abstract Factory
• Promove a consistência entre produtos
– Produtos de uma determinada família devem funcionar conjuntamente e não misturados com produtos de outra família
– O padrão permite implementar esta restrição com facilidade
Consequências do uso do padrão Abstract Factory
• Do lado negativo: dar suporte a novos tipos de produtos é difícil
– O motivo é que a FactoryAbstrata fixa o conjunto de produtos que podem ser criados
– Dar suporte a mais produtos força a extensão da interface da factory o que envolve mudanças na FactoryAbstrata e em todas suas subclasses FactoryConcreta
Considerações de implementação
• Factory como padrão Singleton
– Uma aplicação normalmente só precisa de uma única
instância de uma FactoryConcreta por família de produtos – O padrão Singleton ajuda a controlar a instância única
• Criação dos produtos
– A FactoryIF apenas define a interface de criação – Quem cria os objetos são as FactoryConcreta
• Tais subclasses são freqüentemente implementadas usando o padrão Factory Method
• Uma FactoryConcreta faz override do Factory Method de cada produto
PADRÕES DE CRIAÇÃO
Discussão geral de padrões de criação
• Ajudam a deixar o sistema independente de como seus objetos são criados, compostos e representados • São dois tipos:
– Padrões de criação via classes
• Usam herança para variar a classe que é instanciada • Exemplo: Factory Method
– Padrões de criação via objetos
• Delegam a instanciação para outro objeto • Exemplo: Abstract Factory
Discussão geral de padrões de criação
• Composição é usada mais que herança para estender funcionalidade e padrões de criação ajudam a lidar com a complexidade de criar comportamentos
– Em vez de codificar um comportamento estaticamente, definimos pequenos comportamentos padrão e usamos composição para definir comportamentos mais complexos
– Isso significa que instanciar um objeto com um comportamento particular requer mais do que simplesmente instanciar uma
classe
– Eles escondem como instâncias das classes concretas são
criadas e juntadas para gerar "comportamentos" (que podem envolver vários objetos compostos)
– Os padrões mostrados aqui mostram como encapsular as coisas de forma a simplificar o problema de instanciação
Discussão geral de padrões de criação
• Os padrões de criação discutem temas recorrentes:
– Eles encapsulam o conhecimento das classes concretas que são instanciadas
• Lembre que preferimos nos "amarrar" a interfaces (via interface ou classes abstratas) do que a classes concretas
• Isso promove a flexibilidade de mudança (das classes concretas que são instanciadas)
Perguntas finais para discussão
• Na seção de implementação deste padrão, os autores (Gamma et al.) discutem a idéia de definir factories
extensíveis. Já que uma Abstract Factory consiste de
Factory Methods e que cada Factory Method tem apenas uma assinatura, isto significa que o Factory Method só pode criar um objeto de uma única
Perguntas finais para discussão
• Considere o exemplo de construção de labirintos. O FactoryDeLabirinto contém um método criaSala() que recebe um inteiro como parâmetro,
representando o número da sala. O que acontece se você também quer especificar o tamanho e cor da sala? Isto significa que você deveria criar um novo Factory Method para o FactoryDeLabirinto,
permitindo passar como parâmetros o número da sala, tamanho e cor para um outro método
Perguntas finais para discussão
• Claro que nada impediria que alterasse a cor e
tamanho da sala depois que tiver sido instanciada, mas isso deixaria o código mais "cheio e sujo",
especialmente se você estiver criando e
configurando muitos objetos. Como reter o FactoryDeLabirinto e usar apenas um método
criaSala() e acomodar diferentes parâmetros usados por criaSala() para criar e configurar objetos do tipo Sala?
Perguntas finais para discussão
• No exemplo (FactoryDeLabirinto), não há estado a ser mantido na instância única do singleton. Você
acha que um singleton é realmente necessário aqui? O que ocorreria se permitissemos a criação de mais de uma instância?
• No exemplo (FactoryDeLabirinto), o que ocorre se getInstance("...") for chamado mais de uma vez com parâmetros diferentes?