4D のシングルトン

4D 20 R5 は、デベロッパーのための強力な機能を提供します: シングルトンの登場です!

シングルトンデザインパターンでは、アプリケーション全体でアクセス可能なクラスのインスタンスを 1つだけ作成します。

このパターンには、以下のような多くの利点があります:

  • インタープロセス値の提供
  • ユーティリティクラスの作成
  • ファクトリーデザインパターンのベースとして
  • ほかにも多数

この新しいコンセプトの詳細については、このまま読み進めてください!

4D には 2種類のシングルトンがあります: プロセスごとにユニークなシングルトンと、アプリケーション全体でユニークな共有シングルトンです。シングルトンは標準オブジェクトですが、共有シングルトンは共有オブジェクトです。

シングルトンを定義するのは非常に簡単で、クラスのコンストラクターに singleton キーワードを追加するだけです:

singleton Class constructor()

共有シングルトンの定義も非常に簡単で、共有クラスに singleton キーワードを使うだけです:

shared singleton Class constructor()

定義されたシングルトンには、me属性で簡単にアクセスできます:

$singleton:=cs.SingletonClass.me

me属性はシングルトンインスタンスです (他の言語では、主に getInstance() 関数でアクセスします)。シングルトンは、初めてアクセスされたときにインスタンス化されます。

例題

シングルトンはとても便利なので、たくさん使うことになるでしょう。長い説明よりも例題のほうがわかりやすいので、いくつか用意しました。

ユーティリティクラス

ユーティリティクラスという、簡単な例題から始めましょう。ユーティリティクラスは、アプリケーションのいたるところで使われる多くのユーティリティ関数を提供します。これらの関数を 1つのクラスにまとめると、ユーティリティ関数を見つけるのがとても簡単で、複数メソッドに分散されることもないので、コードのメンテナンスに役立ちます。

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

ユーティリティクラスを定義すると、下の行のようなコードでユーティリティ関数を呼び出すことができます:

cs.UtilityClass.me.utilityFunction1()

Websocketサーバーのラッパー

シングルトンのもうひとつの典型的な使い方は、アプリケーションのどこからでもアクセスできるオブジェクトのラッパーを作ることです。このクラスを考えてみましょう:

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

この場合、次の呼び出しによって Webサーバーを開始することができます:

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

次の呼び出しで Webサーバーを終了できます:

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

ファクトリーデザインパターン

3つ目の例題は、ファクトリー (factory = 工場) というデザインパターンの実装です。ファクトリーは、オブジェクトの新規インスタンスの生成を管理します。たとえば、VehicleFactory は Vehicleオブジェクトを生成します (vehicle = 車両):

// Class VehicleFactory
 
property vehicleBuilt:=0 // ファクトリーで生産された車両の数
 
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

シングルトンである VehicleFactory を呼び出すことで、アプリケーションのどこからでも 1行のコードで新しい Vehicleオブジェクトを取得できます:

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

この例題では、新規 Vehicle を返す前に生産車両数をカウントする以外にファクトリーは何もしませんが、基本の発想は伝わると思います。

buildVehicle 関数は (This.vehicleBuilt を増分することで) VehicleFactory を変更します。そのため、関数の呼び出しと終了時に use と end use が自動的に呼び出されるように、shared キーワードも追加する必要があります。

インタープロセス値

シングルトンのもう 1つの典型的な使用法は、アプリケーション全体で使用するインタープロセス値を提供することです。このようなシングルトンの例を示します:

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

すると、グローバル変数にとても簡単にアクセスできるようになります:

cs.InterprocessSingleton.me.version

Tip: アプリケーションの起動時 (通常は On Application Startup メソッド内で) にインタープロセス値を初期化したい場合があります。それには、下の例のように、シングルトンのコンストラクターに引数を渡す必要があるかもしれません:

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

上の例では、version と buildNumber の値は JSONファイルから取得しています。シングルトンの new() 関数を呼び出す際に、コンストラクターに引数を渡して呼び出すことができます。たとえば、上の例の場合は次のようになります:

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

注意しなければならないのは、new() の呼び出しがシングルトンに初めてアクセスしたときであることです。シングルトンがすでにアクセスされた後にnew() を呼び出だしても、クラスのコンストラクターは呼び出されません。

特別な感謝

シングルトンにアクセスする me属性を追加することを提案してくれた Chris Belanger に感謝の意を表します。

私たちにとってそうであるように、シングルトンがあなたにとってもエキサイティングなものであることを願っています。シングルトンについて質問があれば、4Dフォーラムで遠慮なく質問してください。

Nicolas Brachfogel
- プロダクトオーナー & シニアデベロッパー - Nicolas Brachfogelは、2017年にシニアデベロッパーとして4Dに入社しました(4D Serverとネットワークを担当)。Apple Siliconのリリースを管理するプロダクトオーナーとして、ユーザーストーリーを書いて機能仕様に落とし込み、機能実装が顧客のニーズを満たしているかを確認する役割を担っています。Institut Supérieur d'Informatique Appliquée (INSIA) を卒業した Nicolas は、2001年にソフトウェア開発者としてのキャリアをスタートさせました。JavaとC++で数年間コーディングした後、ゲーム会社のクライアント・サーバー開発を専門に担当。サーバー開発者/アーキテクトとして、多くのゲーム(Dofus Arena、Drakerz、Trivial Pursuit Go!)のサーバーアーキテクチャに携わり、成功を収めてきました。