【保存版】C言語の経験から学ぶオブジェクト指向とV字モデル開発の対応付け
本書は、C言語(構造化手法)における「ファイル・関数単位」の設計経験をベースに、オブジェクト指向(C++)の「クラス・メソッド単位」の設計、およびV字モデル開発への対応付けを体系的に整理したマスタードキュメントです。
1. 開発手法による設計アプローチの決定的な違い
C言語(機能中心)とオブジェクト指向(モノ中心)では、基本設計と詳細設計で決定する「パーツ」の捉え方が異なります。
| 設計フェーズ | C言語(機能・ファイル中心) | オブジェクト指向(モノ・クラス中心) |
|---|---|---|
| 基本設計 |
「機能単位のファイル」の決定 ・システムを構成する機能モジュール( order.c, user.c など)を切り出す。・大枠のデータ構造体( struct)を決める。 |
「モノ単位のクラス(大別・種類)」の決定 ・システムに登場する概念( Customer, Order など)を洗い出す。・共通ルールとなる親クラス(抽象クラス)を定義する。 |
| 詳細設計 |
「ファイル内の関数」の決定 ・ファイル内に必要な具体的な関数( calc_tax(), save_order() など)を洗い出す。・関数内の処理フローを設計する。 |
「クラス内のメソッドと子クラスの処理」の決定 ・親クラスを継承した「子クラス(サブクラス)」を具体化する。 ・各クラス内のメソッド(処理・ロジック)を確定させる。 |
2. オブジェクト指向×C++×V字モデル完全対応
オブジェクト指向の4大概念(抽象化・カプセル化・継承・ポリモーフィズム)が、V字モデルの左側(設計・実装)から右側(テスト)へどのように1対1で対応するかを網羅した一覧です。
| V字モデルのフェーズ | 設計・実装での活動(左側〜底部) | オブジェクト指向の概念とC++での表現 | 対応するテストフェーズ(右側)での検証内容 |
|---|---|---|---|
| 要件定義 | ユーザーの「どうしたいか」を分析し、システムの本質を抜き出す。 |
【抽象化】 ・現実世界の登場人物や業務概念をユースケース図やドメインモデルに落とし込む。 |
システムテスト / 運用テスト ・ユーザーの要求通りにシステム全体が機能するかを検証。 |
| 基本設計 | パーツの大別と種類の決定。 共通ルール(親枠)を決める。 |
【抽象化】【カプセル化】 ・共通窓口を持つ抽象クラス( class Character)の定義・データ隠蔽のルール決定 |
結合テスト(パッケージ結合) ・異なるサブシステム間や、オブジェクトの共通窓口(関数)を介した連携が正しく動くかを検証。 |
| 詳細設計 | 子クラス(種類)の具体的な内部処理や、オブジェクト同士の連携を設計する。 |
【継承】【ポリモーフィズム】 ・親から個別仕様への分岐( Warrior, Wizard)・シーケンス図によるメッセージ連携の設計 |
単体テスト(クラス・モジュール結合) ・【ポリモーフィズムの検証】 ・親のポインタから関数を呼んだ際、実体(子クラス)に応じて正しく処理が切り替わるかを検証。 |
| 実装 | 詳細設計をプログラムに翻訳する。 |
【C++での具体化】 ・ private/public による隠蔽・ : public による継承・ virtual/override による上書き |
単体テスト(メソッド単位) ・【カプセル化の検証】 ・各メソッド単体の入出力検証。 ・ privateなデータが不正に書き換えられないかの検証。 |
3. C++コードによるオブジェクト指向4大概念の実装例
オブジェクト指向の核心となる4つの概念は、C++において以下のように記述されます。
#include <iostream>
#include <string>
// =========================================================================
// ④ 抽象化(Abstraction): 共通の本質だけを抜き出した「概念(大枠)」の設計
// =========================================================================
class Character {
// ① カプセル化(Encapsulation): 外部から直接データを触らせず、安全な窓口だけを公開
private:
std::string name;
int hp;
public:
Character(std::string n, int h) : name(n), hp(h) {}
// カプセル化されたデータにアクセスするための公開窓口(メンバ関数)
std::string getName() const { return name; }
int getHp() const { return hp; }
// ③ ポリモーフィズムのための「純粋仮想関数」
// 基本設計(親)では「攻撃できる」という枠組みだけを決め、中身の処理は詳細設計(子)に強制する
virtual void attack() = 0;
virtual ~Character() {} // 仮想デストラクタ
};
// =========================================================================
// ② 継承(Inheritance): 親(Character)の資産を引き継ぎ、独自の「種類」を作る
// =========================================================================
class Warrior : public Character {
public:
Warrior(std::string n, int h) : Character(n, h) {}
// ③ ポリモーフィズム(Polymorphism): 親の attack を子クラス専用の処理に「上書き」
// 詳細設計で「戦士の具体的な処理ロジック」を作り込む部分
void attack() override {
std::cout << getName() << " は大剣で豪快に斬りつけた!" << std::endl;
}
};
class Wizard : public Character {
public:
Wizard(std::string n, int h) : Character(n, h) {}
// 詳細設計で「魔法使いの具体的な処理ロジック」を作り込む部分
void attack() override {
std::cout << getName() << " は呪文を唱え、炎を放った!" << std::endl;
}
};
// =========================================================================
// メイン処理(検証)
// =========================================================================
int main() {
// 結合テストの視点:異なる子クラスを「親クラス(Character)」として一括で扱う
Character* party[2];
party[0] = new Warrior("アルス", 150);
party[1] = new Wizard("マローネ", 80);
// ポリモーフィズムの真骨頂:同じ「attack()」の呼び出しで、実体に応じた個別の処理が走る
for (int i = 0; i < 2; ++i) {
party[i]->attack();
}
delete party[0]; delete party[1];
return 0;
}
4. オブジェクト指向を導入する開発プロセス上のメリット
-
高いトレーサビリティ(追跡可能性)
要件定義の「アクター」が基本設計の「親クラス」になり、詳細設計の「子クラス」へと直結し、そのまま実装コードのclassになります。V字モデルの左から右(テスト)へ移る際、「何をテストすればよいか」の迷いがなくなります。 -
バグの局所化(カプセル化の効果)
データと関数がクラス内に閉じ込められている(private)ため、単体テストでデータ不正が見つかった際、原因の調査範囲はそのクラスの内部だけに限定され、C言語のように「どこかのファイルがグローバル変数を書き換えていた」という迷宮入りを防げます。 -
拡張時の安全設計(継承・ポリモーフィズムの効果)
新しい「種類(子クラス)」を追加する際、既存のファイルや関数(親クラス側)にif/switch文を追加する必要がありません。新しい子クラスのファイルを1つ独立して作成(差分開発)するだけで済むため、既存機能へのデグレード(先祖返り・バグ混入)を完全に防げます。