ときおり扱い難い定義のデータベースを扱う必要が出てきます。特定のカラムによって値の意味や他テーブルとの関係が大きく変わるテーブルはその一つです。
例えば、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パターンの活用です。