Databaseパターン
Databaseパターンは私が考えたオブジェクト指向のデザインパターンです。アプリケーション内部に共通データベースを用意し、データベースを介してデータを共有することで、オブジェクト間の関係を疎に保ちます。
このパターンは見方によってはバッドプラクティスの勧めであり、一部のプログラマーからご批判を受けるであろうことが予測されます。しかしこのパターンにより救われるプログラマーは少なくないと思われますので、批判を承知で公開します。
このパターンが解決しようとしている領域は、非常に大規模なプログラムで生じる問題です。企業が数十名などの規模のエンジニアを投じ、10年、20年という単位でメンテナンスを続けているようなプロジェクトです。
オブジェクト指向は大きなプログラムに秩序を与える優れた方法論ですが、あらゆる規模のプロジェクトに対応できる万能な方法論ではありません。個人レベルの開発ではなかなか到達し得ないような、非常に大規模なプロジェクトになると、ソフトウェアエンジニアリングの新たな世界が見えてきます。そのような状況において生じる問題を、大幅に緩和できる可能性がある手段の1つとして、このパターンをご紹介します。
問題
オブジェクト指向では各オブジェクトに責任の範囲があります。責任はシンプルであることが望ましく、単一責任の原則などと呼ばれます。これを実現するためには一般的に、メンバ変数を非公開にする情報隠蔽という手段が取られます。
他のオブジェクトが持つ情報にアクセスするには、各オブジェクトが持つインターフェイスを呼び出します。大きなアプリケーションを実装する場合は、大量のクラスやオブジェクトが生成され、その関係は非常に複雑になります。既存のデザインパターンを活用し、設計の経験を積んで感覚を鍛え、UMLなどの図を起こして設計レビューをすることにより、オブジェクト間の関連を整理し、密結合を避けた美しい設計が得られます。
しかしながら、さらに規模が大きくなれば、関連の複雑化は免れません。それは一般的に、密結合、相互参照、バケツリレーなどの形で顕在化します。
解決策
Databaseパターンに従うと、各オブジェクトは中央型の共通データベースを介してデータをお互いに共有できます。
データにアクセスするために、相手のオブジェクトのインスタンスを取得する必要が無いため、オブジェクト間の関連を大幅に削減できます。
グローバル変数とは異なり、別のオブジェクトが持つメンバ変数を直接見たり書いたりすることはできません。データベースの値が書き換えられた場合、それを無視するのか、特定のタイミングでメンバ変数内などに取り込むのかは、まだ各オブジェクトに裁量があります。
このようにDatabaseパターンは、秩序のあるコントロールされたグローバル変数を提供できます。
これは情報隠蔽を部分的に緩和することにより、オブジェクトの関連を疎結合に保てるトレードオフであると言えます。
構造
構造は非常にシンプルです。単一のデータベースオブジェクトを作り、他のオブジェクトはこれを介してデータを共有します。
データベースオブジェクトの実装は、シングルトンパターンまたはプロキシパターンを用いる方法が考えられます。どちらの方法を選ぶのかはデータベースパターンの本質にかかわらない問題ですので、都合の良い方法を選んでください。
実装例
C#の例
using System;
using System.Collections.Generic;
public class Database
{
private static Dictionary<string, object> db;
public object this[string key] { get => db[key]; set => db[key] = value; }
}
public class ObjectA
{
public void DoSomething()
{
var db = new Database();
db["status"] = 100;
}
}
public class ObjectB
{
public void DoSomething()
{
var db = new Database();
Console.WriteLine($"stauts={db["status"]}");
}
}
Javaの例
import java.util.HashMap;
class Database {
private static HashMap<String, Object> db = new HashMap<>();
public Object get(String key) { return db.get(key); }
public void set(String key, Object value) { db.put(key, value); }
}
class ObjectA {
public void doSomething() {
Database db = new Database();
db.set("status", 100);
}
}
class ObjectB {
public void doSomething() {
Database db = new Database();
System.out.println("status=" + db.get("status"));
}
}
長所と短所
複雑な関係をシンプルにできます。
データを見るだけのために、どこかのオブジェクトの奥深くにあるオブジェクトの参照を得る必要が無くなります。
グローバル変数とは異なり、値を取り込むタイミングをオブジェクトがコントロールできます。
グローバル変数的な性質を帯びてしまう可能性があります。オブジェクト指向を熟知していないプログラマーが利用すると、全体の設計が失敗する恐れがあります。
適用
関連が非常に複雑になる、非常に大きなプロセスで使用します。関連の複雑性を十分にコントロールできるような中小規模のプロセスには不要です。
組み込み機器やゲーム機など、プロセスを複数に分割して細分化することが許されず、しかしアプリケーションの規模が非常に大きい場合に、Databaseパターンが必要になる可能性が高いです。
プログラム内に、「Aのデータを管理するクラス」「Bのデータを管理するクラス」…みたいなデータ管理クラスが大量に存在している場合は、Databaseパターンを用いるべきであることを示唆しています。
応用例
階層構造を持たせたい場合、コンポジットパターンを利用するよりも、キー名にネームスペースを導入する方が簡単です。ネームスペースは.(ピリオド)などで名前を区切っただけのもので、単なるキー名のネーミングルールに過ぎません。
オンメモリのキーバリューペアではなく、SQLやNoSQL(Redisなど)、ファイルなどにデータを格納する実装も考えられます。
データベースの値をstring型にして、オブジェクトをJSONにシリアライズして格納することにより、プロセスや言語を超えたデータの共有が可能になります。
むしろ値をobjectではなくstringにした方が汎用性が高くてぶっちゃけ便利です。
複数のプロセスに分割することが可能なプロジェクトでは、プロセス間でデータを共有するために、ファイルやDBを使い、既にDatabaseパターン的な実装になっているかもしれません。