抽象と具体で磨くオブジェクト指向設計
本書は、動くだけのコードを“変化に強い設計”へ引き上げたい開発者のためのガイドです。抽象クラスとインターフェースを使い、具体クラスを差し替えやすくする手法を図解とC#サンプルで解説。小規模リファクタリングから大規模システムまで、保守性と拡張性を同時に得る実践的なヒントを示します。さらにDIコンテナやテストダブルの導入ステップも取り上げ、学習→適用→評価の循環で理解を深めます。一歩上のOOPへ。
目次
抽象と具体を取り込むと、オブジェクト指向はどう変わるか
概念 | 位置づけ | 例(C#) | 主なメリット |
---|---|---|---|
抽象 (Abstraction) | “共通点”だけを宣言し、実装は持たない | interface IMovable { void Move(); } abstract class Animal { public abstract void Speak(); } | 実装を切り離して 設計を安定化/依存方向を逆転できる |
具体 (Concretion) | 抽象を“埋める”実体。メモリ上に生成される | class Dog : Animal { public override void Speak() => Console.WriteLine(“ワン"); } | 目的に応じて 差し替え・追加 が容易/再利用しやすい |

ポイント
- 抽象:型定義・契約(interface / abstract class)
- 具体:実装クラスと生成されたインスタンス(new)、あるいは構成オブジェクト
1. “型”と“インスタンス”の二段構造をさらに強化
- これまでも「クラス=設計図」「オブジェクト=実体」と分けて考えてきました。
- 抽象クラス/インターフェースを導入すると、「設計図の設計図」というもう一段上のレイヤが加わり、
- 具象クラス … ロジックを含む
- 抽象クラス/インターフェース … 役割や契約のみという 三層構造 で整理できます。
- IDE 上でも「抽象層だけを参照(依存)し、具体層をプラグインのように差し替える」設計が促進されます。
2. 抽象を挿入すると得られる 3 つの効果
- 依存関係の逆転(Dependency Inversion Principle)
- 上位モジュールが下位の実装詳細に縛られず、テストやリファクタリングが容易。
- 拡張に強い(Open–Closed Principle)
- 新しい具体クラスを追加するだけで振る舞いを拡張。既存コードは“閉じたまま”。
- ポリモーフィズムの全面活用
- List<IMovable> に Car と Dog を混在させ、一括で Move() を呼び出せる。アルゴリズム側は型を意識しない。
3. 具体を“遅延決定”するデザインパターン
パターン | 抽象と具体の分離ポイント | 典型シーン |
---|---|---|
Factory Method / DI コンテナ | 生成を委譲し、どの具体クラスを使うかを遅延決定 | テスト時にモックへ差し替え |
Strategy | アルゴリズムを差し替え | AI の難易度を変更 |
Template Method | 共通処理は上位で、可変部分だけ下位クラスに委譲 | ファイル取込 ↔ パーサー差分 |
4. “抽象”だけに頼りすぎる落とし穴
症状 | 原因 | 処方箋 |
---|---|---|
インターフェースが細切れで多すぎる | “将来必要かも”と 過度な設計 | 実装が2つ以上現れてから抽象化する “YAGNI” |
継承ツリーが深い | 共通化の強迫観念 | 継承より委譲、必要なら Record 構造体 等で表現 |
メタ抽象(IBaseServiceManagerProvider…) | 名称が曖昧 | 名前は 役割+動詞 (ILogSender) にして深掘りを防止 |
5. まとめ ─ 抽象と具体を意識した学習ステップ
- まずは具体だけでアルゴリズムを完成させる
- 重複や 差し替えたいポイントが見えてきたら、そこを抽象化
- 依存逆転(上位はインターフェース参照)へリファクタリング
- DI コンテナやテストダブルで “差し替え体験” を行い、メリットを実感
この流れで学ぶと「抽象⇔具体」の役割分担が腑に落ち、
- 設計の安定性(壊れにくさ)
- 実装の柔軟性(差し替えやテストのしやすさ)をバランス良く手に入れられます。
参考コード(C#)
public interface IAttack
{
void Execute();
}
public class SwordAttack : IAttack
{
public void Execute() => Console.WriteLine("斬りつけた!");
}
public class Player
{
private IAttack _attack;
public Player(IAttack attack) => _attack = attack;
public void Action() => _attack.Execute();
}
// 実行側
var player = new Player(new SwordAttack()); // 具体を注入
player.Action(); // 斬りつけた!
上記では Player は IAttack(抽象)にのみ依存し、SwordAttack を差し替えれば “魔法攻撃” も “遠距離攻撃” も追加コードなしで動作します。
結論
- 抽象 = 契約、具体 = 実装+インスタンス
- 抽象を挿入すると 保守性・拡張性・テスト容易性 が飛躍的に向上
- ただし抽象化は 必要性が生じてから–– 過剰設計は禁物
抽象と具体のレイヤを意識的に組み込むことで、オブジェクト指向は「動くだけのコード」から 変化に強い設計 へと進化します。
訪問数 1 回, 今日の訪問数 4回
ディスカッション
コメント一覧
まだ、コメントがありません