Singletons en 4D

4D 20 R5 ofrece una poderosa funcionalidad para desarrolladores: ¡Singletons!

El diseño patrón singleton crea una instancia única de una clase accesible en toda la aplicación.

Este patrón ofrece muchos beneficios, incluyendo:

  • un wrapper para las variables interproceso,
  • clases utilitarias,
  • una base para el diseño patrón de la fábrica,
  • y muchas más.

Siga leyendo para obtener más información sobre este nuevo concepto.

4D tiene 2 tipos de singletons: los singletons, que son únicos por proceso y los singletons compartidos, que son únicos en toda la aplicación. Los singletons son objetos estándar, mientras que los singletons compartidos son objetos compartidos.

Definir un singleton es extremadamente fácil; basta con añadir la palabra clave singleton al constructor de una clase:

singleton Class constructor()

Definir un singleton compartido también es muy sencillo; basta con utilizar la palabra clave singleton en una clase compartida:

shared singleton Class constructor()

A partir de ese momento, puede acceder fácilmente a sus singletons a través del atributo me:

$singleton:=cs.SingletonClass.me

El atributo me es la instancia del singleton (a menudo accesible a través de la función getInstance() en otros lenguajes). Se instanciará la primera vez que acceda a su singleton.

Ejemplos

Los singletons son muy útiles, y esperamos que empiece a utilizarlos mucho. Pienso que preferirá ejemplos de singletons en lugar de una larga descripción, así que he preparado unos cuantos.

Clase utiliTARIA

Empecemos con un ejemplo sencillo: la clase utilitaria. La clase utilitaria alberga muchas funciones utilitarias que se utilizan en todas partes en su aplicación. Agrupar estas funciones en una sola clase ayuda a mantener su código, ya que es muy fácil encontrar todas sus funciones utilitarias, y no abarrotan sus métodos.

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

A partir de ese momento, puede llamar a sus funciones utilitarias con esta línea:

cs.UtilityClass.me.utilityFunction1()

wrapper Websocket server

Otro uso clásico de los singletons es crear wrappers para los objetos que deben ser accesibles en cualquier parte de su aplicación, como websocket server. Consideremos esta clase:

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

A partir de ese momento, puede iniciar su Webserver llamando a:

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

Y terminarlo llamando a:

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

Fábrica

El tercer ejemplo implementa un patrón de diseño: la fábrica. La fábrica se encarga de crear nuevas instancias de objetos, en este caso, objetos Vehicle procedentes de un 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

Gracias a su estado Singleton, puede llamar a esta VehicleFactory para obtener un nuevo vehiculo desde cualquier parte de su proceso (o aplicación si la hace compartida) con una sola línea:

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

En ese caso, esta fábrica no hace mucho más que contar el número de vehículos construidos antes de devolver su nuevo vehículo, pero creo que capta la idea.

Si quiere que la VehicleFactory sea única en toda su aplicación, necesita añadirle la palabra clave shared. La función buildVehicle modifica el VehicleFactory (incrementando This.vehicleBuilt), por lo que también necesitas añadirle la palabra clave shared para que use y end use sean llamados automáticamente al llamar y salir de la función.

VaRIABLEs entre procesos

Otro uso clásico de los singletons es alojar variables interproceso que utilizará en toda su aplicación. Aquí tiene un ejemplo de singleton:

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

Desde ahí, puede acceder a sus variables globales muy fácilmente, por ejemplo:

cs.InterprocessSingleton.me.version

Consejo: a veces, querrá inicializar sus variables interproceso al inicio de su aplicación, normalmente dentro del método On Application Startup, y en tal caso, puede que necesite pasar parámetros al constructor singleton como en el ejemplo de abajo:

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

En ese caso, los valores de version y buildNumber provienen de un archivo JSON. En el caso de los singletons, hay una forma de llamar al constructor con parámetros llamando a la función new(). Por ejemplo, en nuestro caso:

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

Hay que tener cuidado de que la llamada a new() sea la primera vez que se accede al singleton. Llamar a new() después de que se haya accedido al singleton ya no llamará al constructor de la clase.

Agradecimientos especiales

Quiero agradecer especialmente a Chris Belanger, que sugirió añadir el atributo me para acceder a un singleton.

Espero que los singletons sean tan emocionantes para ustedes como lo son para nosotros. Si tiene alguna pregunta sobre ellos, no dude en hacerla en el foro de 4D.

Nicolas Brachfogel
• Propietario de producto y Desarrollador Senior - Nicolas Brachfogel se unió a 4D en 2017 como Senior Developer (4D Server y networking). Como Product Owner para gestionar el lanzamiento de Apple Silicon, está a cargo de escribir historias de usuario y traducirlas en especificaciones funcionales, así como asegurarse de que las implementaciones de las funcionalidades satisfagan las necesidades del cliente. Diplomado por el Instituto Superior de Informática Aplicada (INSIA), Nicolas comenzó su carrera como desarrollador de software en 2001. Tras varios años codificando en Java y C++, pasó a especializarse en el desarrollo cliente-servidor para empresas de videojuegos. Como desarrollador/arquitecto de servidores, trabajó con éxito en las arquitecturas de servidores de muchos juegos (Dofus Arena, Drakerz, Trivial Pursuit Go!).