Singletons em 4D

4D 20 R5 oferece uma caraterística poderosa para desenvolvedores: Singletons!

O padrão de design singleton cria uma única instância de uma classe acessível em toda a sua aplicação.

Esse padrão oferece muitos benefícios, incluindo:

  • host para valores entre processos,
  • classes utilitárias,
  • uma base para o padrão de projeto fábrica,
  • e muito mais.

Continue lendo para mais informações sobre esse novo conceito!

4D tem 2 tipos de singletons: singletons que são únicos por processo e singletons compartilhados, que são únicos por toda a aplicação. Singletons são objetos padrão, enquanto singletons compartilhados são objetos compartilhados.

Definir um singleton é extremamente fácil; basta adicionar a palavra-chave singleton a um construtor de classe:

singleton Class constructor()

Definir um singleton partilhado também é muito fácil; basta utilizar a palavra-chave singleton numa classe partilhada:

shared singleton Class constructor()

A partir desse momento, pode acessar facilmente aos seus singletons através do atributo me:

$singleton:=cs.SingletonClass.me

O atributo me é a instância do singleton (frequentemente acessada através da função getInstance() noutras linguagens). É instanciado na primeira vez que acesse seu singleton.

Exemplos

Os singletons são muito úteis, e esperamos que comece a usá-los muito. Penso que prefere exemplos de singletons em vez de uma longa descrição, por isso preparei alguns.

Classe utilitária

Vamos começar com um exemplo simples: a classe utilitária. A classe utility aloja muitas funções utilitárias que são utilizadas em todo o lado na sua aplicação. Agrupar estas funções numa única classe ajuda a manter o seu código, uma vez que é muito fácil encontrar todas as suas funções utilitárias e estas não sobrecarregam os seus métodos.

//Classe UtilityClass
 
singleton Class constructor()
 
Function utilityFunction1()
 
Function utilityFunction2()
 
...

A partir desse momento, pode chamar as suas funções utilitárias com esta linha:

cs.UtilityClass.me.utilityFunction1()

Wrapper do servidor Websocket

Outra utilização clássica dos singletons é criar invólucros para objetos que devem estar acessíveis em qualquer parte da sua aplicação, como um servidor de websocket. Vamos considerar esta classe:

//WebSocketServerWrapper
 
singleton Class constructor()
 
$handler:=cs.myWebsocketServerHandler.new()
This.webSocketServer:=4D.WebSocketServer.new($handler; {dataType: “object”})
 
Function terminate()
 
This.webSocketServer.terminate()

A partir desse momento, pode iniciar o seu servidor Web chamando::

CALL WORKER(“WebSocketServerWorker”; Formula(cs.WebSocketServerWrapper.me))

E encerrá-lo chamando:

CALL WORKER(“WebSocketServerWorker”; Formula(cs.WebSocketServerWrapper.me.terminate()))

Factory

O terceiro exemplo implementa um padrão de design: a fábrica. A fábrica é responsável por criar novas instâncias de objetos, neste caso, objetos Veículo provenientes de uma VehicleFactory:

//Classe VehicleFactory
 
property vehicleBuilt:=0 //Número de veículos construídos pela fábrica
 
shared singleton Class constructor()
 
shared Function buildVehicle($type : Text)->$vehicle : cs.Vehicle
 
  Case of
    : $type="car"
      $vehicle:=cs.Car.new()
    : $type="truck"
      $vehicle:=cs.Truck.new()
    : $type="sport car"
      $vehicle:=cs.SportCar.new()
    : $type="motorbike"
      $vehicle:=cs.Motorbike.new()
  Else
    $vehicle:=cs.Car.new()
  End case
 
  This.vehicleBuilt+=1

Graças ao fato de ser um Singleton, pode chamar esta VehicleFactory para obter um novo Veículo a partir de qualquer ponto do seu processo (ou aplicação, se o tornar partilhado) com uma única linha:

$vehicle:=cs.VehicleFactory.me.buildVehicle("truck")

Nesse caso, esta fábrica não faz muito para além de contar o número de veículos construídos antes de devolver o seu novo veículo, mas penso que já percebeu a ideia.

Se quiser que a VehicleFactory seja única em toda a sua aplicação, tem de lhe acrescentar a palavra-chave shared. A função buildVehicle modifica a VehicleFactory (incrementando This.vehicleBuilt), pelo que também é necessário adicionar-lhe a palavra-chave shared para que use e end use sejam chamadas automaticamente ao chamar e sair da função.

Valores entre processos

Outra utilização clássica dos singletons é alojar valores interprocessuais que serão utilizados em toda a aplicação. Aqui está um exemplo de um singleton desse tipo:

//Classe InterprocessSingleton
 
shared singleton Class constructor()
 
This.version:=2
This.buildNumber:=1056

A partir daí, você pode acessar suas variáveis globais muito facilmente, por exemplo:

cs.InterprocessSingleton.me.version

Dica: Por vezes, pretende inicializar os valores interprocessos no arranque da aplicação, normalmente dentro do método On Application Startup e, nesse caso, poderá ter de passar parâmetros para o construtor do singleton, como no exemplo abaixo:

//Classe InterprocessSingleton
 
shared singleton Class constructor($file : 4D.File)
 
$json:=$file.getText()
$settingsJson:=JSON Parse($json)
 
This.version:=$settingsJson.version
This.buildNumber:=$settingsJson.buildNumber

Nesse caso, os valores de version e buildNumber vêm de um arquivo JSON. No caso de singletons, há uma maneira de chamar o construtor com parâmetros chamando a função new(). Por exemplo, no nosso caso:

$myFile:=File("/RESOURCES/settings.json")
cs.InterprocessSingleton.new($myFile)

É preciso ter cuidado para que a chamada a new() seja a primeira vez que o singleton é acessado. Chamar new() depois de o singleton ter sido acessado já não chamará o construtor da classe.

Agradecimentos especiais

Quero fazer um agradecimento especial a Chris Belanger, que sugeriu adicionar o atributo me para aceder a um singleton.

Espero que os singletons sejam tão interessantes para si como são para nós. Se tiver alguma questão sobre eles, sinta-se à vontade para perguntar no fórum 4D.

Nicolas Brachfogel
• Proprietário do produto e Desenvolvedor Senior -Nicolas Brachfogel entrou a 4D em 2017 como Senior Developer (4D Server e Networking). Como Product Owner para gerenciar o lançamento de Apple Silicon, está a cargo de escrever as histórias dos usuários e depois traduzi-las em especificações funcionais, além de garantir que as implementações de funcionalidade cumpram com as necessidades do cliente. Diplomado pelo Instituto Superior de Informática Aplicada (INSIA), Nicolas começou sua carreira como desenvolvedor de software em 2001. Depois de vários anos codificando em Java e C++, passou a especializar-se no desenvolvimento cliente-servidor para empresas de videogames. Como desenvolvedor/arquiteto de servidores, trabalhou com sucesso nas arquiteturas de servidores de muitos jogos (Dofus Arena, Drakerz, Trivial Pursuit Go!)