Singoli in 4D

Tradotto automaticamente da Deepl

4D 20 R5 offre una potente funzionalità agli sviluppatori: I singleton!

Il design pattern singleton crea una singola istanza di una classe accessibile in tutta l’applicazione.

Questo pattern offre molti vantaggi, tra cui:

  • host per valori interprocesso,
  • classi di utilità,
  • una base per il design pattern factory,
  • e molti altri.

Continuate a leggere per maggiori informazioni su questo nuovo concetto!

4D ha due tipi di singleton: i singleton, che sono unici per processo, e i singleton condivisi, che sono unici per tutta l’applicazione. I singleton sono oggetti standard, mentre i singleton condivisi sono oggetti condivisi.

Definire un singleton è estremamente semplice: basta aggiungere la parola chiave singleton al costruttore di una classe:

singleton Class constructor()

Anche la definizione di un singleton condiviso è molto semplice: basta usare la parola chiave singleton in una classe condivisa:

shared singleton Class constructor()

Da quel momento in poi, si può facilmente accedere ai singleton attraverso l’attributo me:

$singleton:=cs.SingletonClass.me

L’attributo me è l’istanza del singleton (spesso accessibile tramite la funzione getInstance() in altri linguaggi). Viene istanziato la prima volta che si accede al singleton.

Esempi

I singleton sono molto utili e ci aspettiamo che li usiate spesso. Penso che preferireste degli esempi di singleton piuttosto che una lunga descrizione, quindi ne ho preparati alcuni.

Classe di utilità

Cominciamo con un esempio semplice: la classe utility. La classe utility ospita molte funzioni di utilità che vengono utilizzate ovunque nella vostra applicazione. Raggruppare queste funzioni in un’unica classe aiuta a mantenere il codice, poiché è molto facile trovare tutte le funzioni di utilità e non ingombrano i metodi.

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

Da questo momento in poi, si possono chiamare le funzioni di utilità con questa riga:

cs.UtilityClass.me.utilityFunction1()

Websocket server wrapper

Un altro uso classico dei singleton è quello di creare wrapper per oggetti che devono essere accessibili ovunque nell’applicazione, come un server websocket. Consideriamo questa classe:

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

Da quel momento in poi, è possibile avviare il server Web chiamando:

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

E terminarlo chiamando:

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

Fabbrica

Il terzo esempio implementa un modello di progettazione: il factory. La fabbrica ha il compito di creare nuove istanze di oggetti, in questo caso oggetti Veicolo provenienti da una VehicleFactory:

//Class VehicleFactory
 
property vehicleBuilt:=0 //Number of vehicles built by the factory
 
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

Grazie al fatto di essere un singleton, è possibile chiamare questa VehicleFactory per ottenere un nuovo veicolo da qualsiasi punto del processo (o dell’applicazione, se la si rende condivisa) con una sola riga:

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

In questo caso, questa fabbrica non fa molto oltre a contare il numero di veicoli costruiti prima di restituire il nuovo veicolo, ma credo che si capisca l’idea.

Se si vuole che la VehicleFactory sia unica per tutta l’applicazione, occorre aggiungere la parola chiave shared. La funzione buildVehicle modifica la VehicleFactory (incrementando This.vehicleBuilt), quindi è necessario aggiungere anche la parola chiave shared, in modo che use e end use vengano richiamati automaticamente quando si chiama e si esce dalla funzione.

Valori interprocesso

Un altro uso classico dei singleton è quello di ospitare valori interprocesso che verranno utilizzati in tutta l’applicazione. Ecco un esempio di singleton di questo tipo:

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

Da qui, ad esempio, è possibile accedere alle variabili globali in modo molto semplice:

cs.InterprocessSingleton.me.version

Suggerimento: a volte si desidera inizializzare i valori interprocesso all’avvio dell’applicazione, in genere all’interno del metodo On Application Startup; in questo caso, potrebbe essere necessario passare dei parametri al costruttore del singleton, come nell’esempio seguente:

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

In questo caso, i valori di version e buildNumber provengono da un file JSON. Nel caso dei singleton, c’è un modo per chiamare il costruttore con i parametri, richiamando la funzione new(). Ad esempio, nel nostro caso:

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

Bisogna fare attenzione che la chiamata a new() sia la prima volta che si accede al singleton. Chiamando new() dopo l’accesso al singleton non si chiamerà più il costruttore della classe.

Ringraziamenti speciali

Voglio fare un ringraziamento speciale a Chris Belanger, che ha suggerito di aggiungere l’attributo me per accedere a un singleton.

Spero che i singleton siano entusiasmanti per voi come lo sono per noi. Se avete domande in merito, non esitate a farle sul forum di 4D.

Nicolas Brachfogel
- Proprietario del prodotto e sviluppatore senior - Nicolas Brachfogel è entrato in 4D nel 2017 come Senior Developer (4D Server e networking). In qualità di Product Owner per gestire il rilascio di Apple Silicon, si occupa di scrivere le storie degli utenti e di tradurle in specifiche funzionali, nonché di assicurarsi che le implementazioni delle funzionalità soddisfino le esigenze dei clienti. Diplomato all'Institut Supérieur d'Informatique Appliquée (INSIA), Nicolas ha iniziato la sua carriera come sviluppatore di software nel 2001. Dopo diversi anni di codifica in Java e C++, si è specializzato nello sviluppo di client-server per aziende di videogiochi. Come sviluppatore/architetto di server, ha lavorato con successo alle architetture server di molti giochi (Dofus Arena, Drakerz, Trivial Pursuit Go!).