6
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

ちょうぜつAdvent Calendar 2022

Day 11

InheritanceMappers (SingleTableInheritance vs ClassTableInheritance vs ConcreteTableInheritance)

Posted at

11.InheritanceMappers (SingleTableInheritance vs ClassTableInheritance vs ConcreteTableInheritance).png

継承による類似オブジェクトの派生/汎化をマッピングするパターンです。パターンの違いは、テーブルとクラスの対応における数の違いです。

Single Table Inheritance は、ひとつのテーブルで複数の派生クラスに対応させるパターンです。クラスとテーブルの数で言うと 1+N : 1 になります。このパターンがもっとも扱いやすい継承マッピングと言えます。

テーブルにレコードの種類を表す type カラムがあったとします。それを継承マッピングせずに扱うと、次のようなコードがそこらじゅうに発生します。

switch(record.type) {
    case 1:
        // 1 の場合の処理
        break;
    case 2:
        // 2 の場合の処理
        break;
    case 3:
        // 3 の場合の処理
        break;
        :
        :

オブジェクト指向を好むプログラマーがもっとも嫌う switch 文です。せっかくオブジェクトの多態性を利用できるプログラム言語を使っていても、このような記述が必要になると台無しです。決まった record のメソッドをひとつ呼ぶ、あるいは、抽象型で受け取る関数の引数にして、まったく同じコードでありながら、type の違いで勝手に異なる処理にディスパッチされる。こう考えてモデリングできる方が、どう考えてもスマートな Domain Model ですよね。

Data Mapper がオブジェクトを生成する時点で、具象としてどのクラスのインスタンスを生成するのかを隠蔽するファクトリになれば、あっさりとこのイライラを解消できます。type を見て分岐するのは、生成の 1 箇所だけ (Metadata Mapping をすればたった 1 つの分岐ロジックも消える) になります。扱うエンティティは、共通の抽象クラスのかたちに一般化できます。

Single Table Inheritance は、多のパターンのように JOIN や UNION を必要とする特徴がないので、愚直にテーブルと直結する Active Record でも可能です。自身の type で分岐するロジックが現れたときは、Active Record においても検討する価値はあります。

Single Table Inheritance の弱点はカラムの冗長性です。派生クラスは、種類によって少しづつ異なるフィールドを持つかもしれません。たとえば、同じように扱える抽象商品も、売り方によって、消費税があったり関税があったりします。けれどテーブルは 1 つしかないので、片方に値が入ればもう片方は NULL になる、といった歯抜けのカラムができてしまいます。

リレーショナルデータとして歯抜けが多いのは問題なので、それを防ごうとするのが、Class Table Inheritance と Concrete Table Inheritance です。これらは、派生した具象クラスが増えるごとに、テーブルも同じ数だけ増えます。数の関係は N:N または N+1:N になります。

Class Table Inheritance は、共通抽象クラスに 1 テーブル、派生クラスの個々に 1 テーブルづつを割り当てるパターンです。全クラスの総数とテーブルの数は同じになります。

共通部分がほとんどで、派生による差異が小さい場合には、Class Table Inheritance が有効です。抽象クラスに共通したことを実装するのが効率的なのと同様に、データベースでも、共通部分の構造を共有したほうが効率的ですね。

Class Table Inheritance の弱点は多数のテーブルが作る複雑さです。多段階の継承とデータベースをマッピングすると、ひとつの意味を表すレコードが、いくつものテーブルに分散してしまいます。SQL で意味のある表を作ろうと思ったら、毎回間違わないように JOIN しないといけません。また、パフォーマンスチューニングも困難になります。場合によって (共通性があまりにも小さくて、同種のものとして扱う必要がないぐらい別物感があるとき) は、正規化しすぎるよりも、少しぐらいの冗長性を受け入れたほうが、トータルで得になる場合があります。

Concrete Table Inheritance は、Class Table Inheritance と似ているけれど、その抽象クラス用テーブルを設けないバージョンになります。テーブルは具象クラスとだけ対応します。このパターンにすれば、意味のあるデータが一式、ひとつのテーブルに収まります。

抽象クラスが持つ属性は、それぞれのテーブルが冗長的に持つことになります。共通部分が冗長になるので、このパターンは Class Table Inheritance とは逆に、共通部分が小さく、派生による差が大きいときに有効です。

Concrete Table Inheritance の弱点は、意味が同じはずのカラムが別のテーブルに入るため、そこにユニーク制約がかからないことです。また、共通した属性を SQL の集計関数で扱いたい場合にも、やりにくさとパフォーマンス問題が出てくる可能性があります。共通カラムだけを SELECT した表を UNION してようやく、Class Table Inheritance や Single Table Inheritance と対等になります。継承関係がなくてもいいぐらい、派生クラスの共通性が本当に少ない場合にのみオススメできます。

これらのパターンの選択の難しさは、継承が特殊だからと言うよりも、リレーショナルデータの難しさに由来しています。プログラミングによる何でもありの自由なモデリングに対して、リレーショナルデータベースの考え方は、状況特化していて特殊なのです (怒られるかもしれませんがこう表現するしかなくて申し訳ない)。おそらく継承は、エンティティかスナップショットかと並ぶ、最大のインピーダンス差異です。継承が適しているほど高度な Domain Model になった場合は、外部化された Data Mapper を使って、モデルの開発におけるインピーダンスミスマッチを最小化しましょう。

どの継承マッピングを選択しても、モデルクラス側のコードは変わりません。継承構造に対するデータのマッピングは、いずれもパターンにも共通性があります。Inheritance Mappers は、厳密に言うと (必要となったら他に選択の余地がないので) パターンとは言えず、似ていたり違っていたりする各種の継承マッピングの一般的な技法を総称する、あるいは実現するレイヤーの、名前です。(←と認識しているんだけど、なぜ著者のファウラーがこう章立てしたのか、実は理解していません)

6
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?