9. 継承より委譲を用いる
OOPには継承という強力な概念があります。継承によって、共通の処理を親クラスに書き、子クラスが継承することで処理を再利用することができるようになります。しかし、単に差分プログラミングを行うためだけに継承を利用してはなりません。
クラスの継承よりオブジェクトのコンポジションを好め
(Favor object composition over class inheritance.)
Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides(GoF)
継承は、is-a関係が成り立つ場合に行うものです。もしこれが成り立たないのに、単に繰り返しを避けるためだけに継承を用いると、子クラスが必要としないものまで無理やり継承させられてしまう恐れがあります。そしてそれはリスコフの置換原則に違反することも意味します。また、継承をすると子クラスが親クラスと強く結合してしまいます。親クラスの詳細を知ってしまい、親クラスの変更による影響をモロに受けてしまいます。
csharp
// 😢 悪い例:移動機能を再利用したいだけなのに継承してしまう
public abstract class MovableCharacter : MonoBehaviour
{
protected float speed;
protected void MoveToward(Vector3 target)
{
transform.position = Vector3.MoveTowards(
transform.position, target, speed * Time.deltaTime);
}
}
public class Player : MovableCharacter // Player "is-a" MovableCharacter ?
{
void Start() => speed = 5f;
void Update()
{
var target = GetInputTarget();
MoveToward(target);
}
}
public class Enemy : MovableCharacter // Enemy "is-a" MovableCharacter ?
{
void Start() => speed = 3f;
void Update() => MoveToward(playerTransform.position);
}
// 💥 問題1: 移動しないボス(ワープ型)を作りたい
// → MovableCharacterを継承すると speed や MoveToward() が無駄に付いてくる
// → 使わない機能を持つのはリスコフの置換原則に違反する
// 💥 問題2: HP管理の機能も共通化したい
// → C#は多重継承できないので、別の基底クラスを同時に継承できない!
// 💥 問題3: MovableCharacterの実装を変えると全子クラスに影響が波及する単に共通化を行いたいだけなら、継承ではなく委譲で対応するべきです。
csharp
// ✅ 良い例:移動機能を独立したクラスに切り出し、委譲する
public class Mover
{
readonly Transform transform;
readonly float speed;
public Mover(Transform transform, float speed)
{
this.transform = transform;
this.speed = speed;
}
public void MoveToward(Vector3 target)
{
transform.position = Vector3.MoveTowards(
transform.position, target, speed * Time.deltaTime);
}
}
public class Player : MonoBehaviour
{
Mover mover; // 移動機能を「持っている」(has-a) ✅
void Start()
{
mover = new Mover(transform, speed: 5f);
}
void Update()
{
var target = GetInputTarget();
mover.MoveToward(target); // 移動処理はMoverに委譲
}
}
public class Enemy : MonoBehaviour
{
Mover mover; // 同じ移動機能を再利用 ✅
void Start()
{
mover = new Mover(transform, speed: 3f);
}
void Update() => mover.MoveToward(playerTransform.position);
}
// ✅ ワープ型ボスはMoverを持たなければいい → 不要な機能を背負わない
// ✅ HP管理も別クラスにして自由に組み合わせられる → 多重継承の制約なし
// ✅ Moverの内部実装が変わっても、公開メソッドが同じなら呼び出し側に影響しないもちろん継承が絶対悪というわけではありません。継承を使うべき場面もちゃんとあります。ただし、迷ったら委譲を選ぶのが無難です。