単一テーブル継承(STI)/クラステーブル継承(CTI)/具象クラス継承(CCI)について整理する
こんにちは、Webエンジニアのまさぴょんです!
今回は、同じチームの先輩エンジニアから教えてもらった、
オブジェクト指向プログラミングにおけるクラスの継承戦略について整理して、理解を深めていきたいと思いまっする💪🥺🔥
クラス継承のORMパターンとは?
オブジェクト指向プログラミングにおけるクラスの継承関係をリレーショナルデータベースにマッピングする際、次の3つの主要な戦略があります👀✨
- 単一テーブル継承(STI:Single Table Inheritance)
- クラステーブル継承(CTI:Class Table Inheritance)
- 具象クラス継承(CCI:Concrete Class Inheritance)
STIも含めたこれら3つは、「オブジェクト指向設計で抽出されたスーパークラス・サブクラスから成る継承階層をリレーショナルデータベースのテーブルとして実装するためのパターン」という意味で同類です。
そう、STIとはRails独自のものではなく、ORマッピングのための汎用的な手法のひとつだったのです。
クラス継承のORMパターンの3つは、引用に記載あるとおり、オブジェクト指向プログラミングの継承関係をリレーショナルデータベースにマッピングする際の手法です📝
今回の記事では、単一テーブル継承(STI)/クラステーブル継承(CTI)/具象クラス継承(CCI)、
それぞれの実装方法や簡単なER図、メリット・デメリット、適したユースケースについて整理していきます🤔
1. 単一テーブル継承(STI: Single Table Inheritance)
単一テーブル継承(STI:Single Table Inheritance)は、すべてのクラス階層(親クラスとそのサブクラス)のデータを一つのテーブルに格納するパターンです。
単一テーブル継承(STI)の特徴は、次のとおり👀✨
- テーブルには、全てのサブクラスのフィールドが含まれます。
- レコードがどのサブクラスに属するかを識別するために、 ディスクリミネータカラム(例:typeカラム) を使用します。
- サブクラスに固有のフィールドは、他のサブクラスのレコードでは
NULL
となります。
単一テーブル継承(STI)のER図
+--------------------------------------+
| Animals |
+--------------------------------------+
| id (主キー) |
| type (ディスクリミネータ) | <-- ex) "Dog", "Cat"など
| name (共通フィールド) |
| dog_type (Dog専用) |
| cat_type (Cat専用) |
+--------------------------------------+
# つまり、抽象化すると、こういうこと👀✨
+--------------------------------+
| エンティティ(単一テーブル) |
+--------------------------------+
| ID (主キー) |
| タイプ (ディスクリミネータ) |
| 共通フィールドA |
| 共通フィールドB |
| サブクラス1のフィールドX | <-- サブクラス2では、null
| サブクラス2のフィールドY | <-- サブクラス1では、null
+--------------------------------+
単一テーブル継承(STI)のメリット
- クエリがシンプルで高速:結合(JOIN)が不要なため、データ取得が迅速。
- テーブル管理が容易:テーブルが一つだけなので、管理やメンテナンスが簡単。
単一テーブル継承(STI)のデメリット
-
NULL値が多い:サブクラス固有のフィールドが他のサブクラスでは
NULL
になる。 - テーブルの肥大化:サブクラスやフィールドが増えると、テーブルの構造が複雑化。
- データの整合性リスク:不適切なフィールドの組み合わせが入力される可能性がある。
単一テーブル継承(STI)が適したユースケース
- サブクラスの数が少なく、フィールドの違いが小さい場合。
- パフォーマンスを重視し、シンプルなクエリが求められる場合。
2. クラステーブル継承(CTI:Class Table Inheritance)
クラステーブル継承(CTI:Class Table Inheritance)は、各クラス(親クラスとサブクラス)ごとに個別のテーブルを作成するパターンです。
クラステーブル継承(CTI)の特徴は、次のとおり👀✨
- サブクラスのテーブルは、親クラスのテーブルの主キーを外部キー(または主キーとして)持ちます。
- データ取得時には、親クラスとサブクラスのテーブルを 結合(JOIN) します。
クラステーブル継承(CTI)のER図
クラステーブル継承(CTI)では、次のようなAnimalsという親クラス(親テーブル)がある場合に、
Dogsや、Catsのようなサブクラス(子テーブル)を作成するような継承パターンになります💪🥺🔥
+--------------------------+
| Animals |
+--------------------------+
| animal_id (主キー) |
| name | <-- サブクラス(Dogs,Cats)でも共通するフィールド
+--------------------------+
+--------------------------+
| Dogs |
+--------------------------+
| dog_id (主キー) |
| animal_id (外部キー) |
| dog_type | <-- Dogs固有のフィールド
+--------------------------+
+--------------------------+
| Cats |
+--------------------------+
| cat_id (主キー) |
| animal_id (外部キー) |
| cat_type | <-- Cats固有のフィールド
+--------------------------+
クラステーブル継承(CTI)のメリット
- データの正規化:重複がなく、データの一貫性が保たれる。
- 拡張性が高い:新しいサブクラスやフィールドの追加が容易。
クラステーブル継承(CTI)のデメリット
- クエリが複雑でパフォーマンスが低下する可能性:データ取得時に複数のテーブルを結合する必要がある。
- 管理の煩雑さ:テーブル数が増えるため、スキーマの管理が複雑になる。
クラステーブル継承(CTI)が適したユースケース
- サブクラスが多く、各サブクラスが多数の固有フィールドを持つ場合。
- データの正規化や整合性が特に重要な場合。
3. 具象クラス継承(CCI:Concrete Class Inheritance)
具象クラス継承(CCI:Concrete Class Inheritance)は、各サブクラスごとに独立したテーブルを作成するパターンです。
具象クラス継承(CCI)の特徴は、次のとおり👀✨
- 親クラスのフィールドも各サブクラスのテーブルに含めます。
- 親クラスのテーブルは存在しません。
具象クラス継承(CCI)のER図
+--------------------------+
| Dogs |
+--------------------------+
| dog_id (主キー) |
| name | <-- 共通するフィールド
| dog_type | <-- Dogs固有のフィールド
+--------------------------+
+--------------------------+
| Cats |
+--------------------------+
| cat_id (主キー) |
| name | <-- 共通するフィールド
| cat_type | <-- Cats固有のフィールド
+--------------------------+
具象クラス継承(CCI)メリット
- クエリがシンプルで高速:結合が不要なため、データ取得が迅速。
- テーブル構造が明確:各サブクラスが独立しているため、理解しやすい。
具象クラス継承(CCI)のデメリット
- データの冗長性:親クラスの共通フィールドが各サブクラスのテーブルに重複する。
- 一貫性の維持が困難:共通フィールドの更新時に、全てのサブクラスのテーブルを修正する必要がある。
具象クラス継承(CCI)が適したユースケース
- サブクラス間で共通フィールドが少ない場合。
- データの冗長性よりも読み取り性能を優先する場合。
単一テーブル継承(STI)/クラステーブル継承(CTI)/具象クラス継承(CCI)の比較表
単一テーブル継承(STI)/クラステーブル継承(CTI)/具象クラス継承(CCI)の3つを比較すると、次のようになります👀🌟
特徴 | 単一テーブル継承 | クラステーブル継承 | 具象クラス継承 |
---|---|---|---|
テーブル数 | 1 | クラス数 | サブクラス数 |
データの冗長性 | 低 | 低 | 高(共通フィールドが重複) |
NULL値の発生 | 高(不要なカラムにNULL) | 低 | 低 |
クエリの複雑さ | 低(JOIN不要) | 高(JOIN必要) | 低(JOIN不要) |
パフォーマンス | 高いが、テーブル肥大化で低下 | JOINにより低下する可能性 | 高(データ冗長性に注意) |
拡張性 | 低 | 高 | 中 |
データの一貫性維持 | リスクあり | 容易 | 困難(冗長性のため) |
適したユースケース | サブクラスが少ない場合 | 正規化が重要な場合 | 読み取り性能を重視する場合 |
3つを比較した上での選択のポイント
3つを比較した上での選択のポイントをまとめると、次のような感じでしょうか🤔
- パフォーマンス重視:単一テーブル継承、具象クラス継承
- データの正規化・整合性重視:クラステーブル継承
- 管理の容易さ:単一テーブル継承
- スキーマの拡張性:クラステーブル継承
まとめ
- 単一テーブル継承:パフォーマンスとシンプルさを重視しますが、スキーマの柔軟性とデータの整合性に課題があります。
- クラステーブル継承:データの正規化と拡張性を提供しますが、クエリが複雑になりパフォーマンスに影響を与える可能性があります。
- 具象クラス継承:クエリがシンプルで高速ですが、データの冗長性と一貫性の維持に注意が必要です。
システムの要件やデータモデルに応じて、これらの特徴を考慮して最適な継承戦略を選択してください🙏
Xやってます、フォローよろしくお願いします🙏
参考・引用
Discussion