ときおり扱い難い定義のデータベースを扱う必要が出てきます。特定のカラムによって値の意味や他テーブルとの関係が大きく変わるテーブルはその一つです。
例えば、usersテーブルにtypeカラムがあり、typeによって仮会員か一般会員か特別会員か区別するとします。それぞれ似た様で確かに違うモノです。仮会員はメールアドレス等いくつかのデータがnullでも良し、の様に別のふるまいが要求され、それが増えてくると、usersテーブルを扱うのはだんだん面倒になってきます。早い時点でuserテーブルをリファクタリングできていれば問題にならないのですが、そうならないまま既存データやコードが積み重なり今更変える時間がない、ということもあります。アダプターパターンを用いることでデータベース定義を変えないままこれを緩和できます。
アダプターパターンは増補改訂版Java言語で学ぶデザインパターン入門 | 結城 浩 |本 | 通販 | Amazonでは次のように説明されています。
「すでに提供されているもの」と「必要なもの」の間の「ずれ」を埋めるようなデザインパターン、これが
Adapterパターン
です。
この考えに従えば「すでに提供されているもの」はtypeによってデータの意味が大きく変わるテーブル、「必要なもの」はデータの意味ごとのテーブルです。多くのフレームワークで採用されているORM(オブジェクト関係マッピング)は1テーブルに1モデルの原則で使いやすい様に作られています。これを1つの意味に対して1モデルになる様に用います。
具体的にはマッピングの時点でwhere句を用いて、あるテーブルの特定のtype1つに対して1モデルの様にします。
例えばLaravelならば
/** User.php */
class User{
// 仮会員、一般会員、特別会員に関する大量のメソッド
}
となっている1つのモデル定義ファイルを明示的なテーブル指定とグローバルスコープによって
/** TemporaryUser.php */
class TemporaryUser{
protected $table = 'users';
public funtion boot(){
parent::boot();
self::addGlobalScope('decide_user_type', static function (Builder $query) {
$query->where('type', config('const.user.type.temporary'));
});
}
// 仮会員に関するメソッド
}
/** GeneralUser.php */
class GeneralUser{
protected $table = 'users';
public funtion boot(){
parent::boot();
self::addGlobalScope('decide_user_type', static function (Builder $query) {
$query->where('type', config('const.user.type.general'));
});
}
// 一般会員に関するメソッド
}
/** SpecialUser.php */
class SpecialUser{
protected $table = 'users';
public funtion boot(){
parent::boot();
self::addGlobalScope('decide_user_type', static function (Builder $query) {
$query->where('type', config('const.user.type.special'));
});
}
// 特別会員に関するメソッド
}
と分割した3ファイルに分割します。完全に共有する部分は、ユーザ特性としてtraitを用意し、3会員モデルクラスそれぞれにuseさせることで整理できます。
テーブルを分割する例を挙げましたが逆に複数のテーブルや設定等をまとめた上でカプセル化されたEntityクラスを作るというのもまた別のAdapterパターンの活用です。