3.1 Singleton

Singleton é um padrão de design genérico que garante que um aplicativo de thread único terá uma única instância de alguma classe e fornece um ponto de acesso global a essa instância.

solteiro

Muitas vezes, os programadores novatos gostam de montar métodos utilitários em alguma classe estática - uma classe que contém apenas métodos estáticos. Essa abordagem tem várias desvantagens - por exemplo, você não pode passar uma referência a um objeto dessa classe, esses métodos são difíceis de testar e assim por diante.

Como alternativa, foi proposta uma solução de classe singleton: uma classe que pode ter apenas um objeto. Ao tentar criar este objeto, ele só é criado se ainda não existir, caso contrário, uma referência a uma instância já existente é retornada.

É fundamental que seja possível utilizar uma instância da classe, pois em muitos casos fica disponível uma funcionalidade mais ampla. Por exemplo, esta classe pode implementar algumas interfaces e seu objeto pode ser passado para outros métodos como uma implementação da interface. O que não pode ser feito com um conjunto de métodos estáticos.

Prós:

  • Os métodos são vinculados a um objeto, não a uma classe estática - você pode passar um objeto por referência.
  • Os métodos de objeto são muito mais fáceis de testar e simular.
  • Um objeto só é criado quando necessário: inicialização preguiçosa do objeto.
  • Acelerar o lançamento inicial do programa se houver muitos singles que não sejam necessários para o lançamento.
  • Sozinho pode ser transformado em uma estratégia de modelo ou vários desses objetos.

Pontos negativos:

  • Torna-se mais difícil controlar corridas e atrasos entre threads.
  • É difícil escrever um “solitário” multiencadeado “da cabeça”: o acesso a um singleton de longa data, idealmente, não deve abrir um mutex. Melhores soluções comprovadas.
  • Um conflito entre dois threads sobre um único thread inacabado resultará em um atraso.
  • Se o objeto estiver sendo criado por muito tempo, o atraso pode interferir no usuário ou atrapalhar o tempo real. Nesse caso, é melhor transferir sua criação para o estágio de inicialização do programa.
  • Recursos especiais são necessários para testes de unidade - por exemplo, para colocar a biblioteca no modo "não solitário" e isolar completamente os testes uns dos outros.
  • É necessária uma tática especial para testar o programa finalizado, pois até mesmo o conceito de “lançabilidade mais simples” desaparece, pois a capacidade de inicialização depende da configuração.

3.2 Fábrica [Método]

Um método de fábrica é um padrão de design genérico que fornece subclasses (herdeiros de classes) com uma interface para criar instâncias de uma determinada classe. No momento da criação, os descendentes podem determinar qual classe criar.

Em outras palavras, esse modelo delega a criação de objetos aos descendentes da classe pai. Isso permite que você não use classes concretas no código do programa, mas sim manipular objetos abstratos em um nível superior.

Método de Fábrica

Esse padrão define uma interface para criar um objeto, mas deixa para as subclasses decidir em qual classe basear o objeto. Um método de fábrica permite que uma classe delegue a criação de subclasses. Usado quando:

  • a classe não sabe antecipadamente quais objetos de quais subclasses ela precisa criar.
  • uma classe é projetada para que os objetos que ela cria sejam especificados por subclasses.
  • a classe delega suas responsabilidades a uma das várias subclasses auxiliares e é planejada para determinar qual classe assume essas responsabilidades.

3.3 Fábrica Abstrata

Uma fábrica abstrata é um padrão de projeto genérico que fornece uma interface para criar famílias de objetos relacionados ou interdependentes sem especificar suas classes concretas.

O padrão é implementado criando uma classe abstrata Factory, que é uma interface para criar componentes do sistema (por exemplo, para uma interface de janela, ela pode criar janelas e botões). Em seguida, são escritas classes que implementam essa interface.

fábrica abstrata

É utilizado nos casos em que o programa deve ser independente do processo e dos tipos de novos objetos criados. Quando for necessário criar famílias ou grupos de objetos relacionados, excluindo a possibilidade de uso simultâneo de objetos de diferentes conjuntos destes no mesmo contexto.

Forças:

  • isola classes específicas;
  • simplifica a substituição de famílias de produtos;
  • garante a compatibilidade do produto.

Digamos que seu programa funcione com o sistema de arquivos. Então, para trabalhar no Linux, você precisa dos objetos LinuxFile, LinuxDirectory, LinuxFileSystem. E para trabalhar no Windwos, você precisa das classes WindowsFile, WindowsDirectory, WindowsFileSystem.

A classe Path, que é criada via Path.of(), é exatamente um desses casos. Path não é realmente uma classe, mas uma interface, e possui implementações WindowsPath e LinuxPath. E que tipo de objeto será criado está oculto no seu código e será decidido em tempo de execução.

3.4 Protótipo

Prototype é um padrão de design generativo.

Esse padrão define os tipos de objetos que são criados usando uma instância de protótipo e cria novos objetos copiando esse protótipo. Permite fugir da implementação e seguir o princípio da “programação através de interfaces”.

Uma interface/classe abstrata no topo da hierarquia é especificada como o tipo de retorno, e as classes descendentes podem substituir um herdeiro que implementa esse tipo lá. Simplificando, esse é o padrão de criação de um objeto clonando outro objeto em vez de criá-lo por meio de um construtor.

Protótipo

O padrão é usado para:

  • evitando o esforço adicional de criar um objeto de forma padrão (ou seja, usando um construtor, pois neste caso também serão chamados os construtores de toda a hierarquia ancestral do objeto), quando isso é proibitivamente caro para a aplicação.
  • evite herdar o criador do objeto no aplicativo cliente, como faz o padrão fábrica abstrata.

Use este padrão de design quando seu programa não se importa como ele cria, compõe e apresenta produtos:

  • classes instanciadas são determinadas em tempo de execução, por exemplo, usando carregamento dinâmico;
  • você deseja evitar a construção de hierarquias de classes ou fábricas paralelas às hierarquias de classes de produtos;
  • instâncias de classe podem estar em um dos vários estados diferentes. Pode ser mais conveniente definir o número apropriado de protótipos e cloná-los, em vez de instanciar manualmente a classe no estado apropriado a cada vez.