学習記事一覧 · C#写経から学ぶ構造の考え方

【学習】複数クラスで止まる理由:依存関係を理解する

← 第4回 | 第5回 | 第6回 →


はじめに

1つのクラスだけのコードはなんとか写せた。でも PlayerEnemy の2つが出てきた瞬間に止まる——これはよくある現象です。

原因は「依存関係」という概念が分かっていないことです。

この記事では、複数のクラスが登場したときに何が起きているのかと、正しい対処を解説します。


複数クラスで止まる場面

例えばこういうコードが出てきます。

class Game
{
    void Start()
    {
        Player player = new Player();
        Enemy enemy = new Enemy();
        player.Attack(enemy);
    }
}

このコードを写そうとしたとき、多くの人がここで止まります。

  • Player クラスをどこに書けばいい?
  • Enemy クラスはどこに書けばいい?
  • player.Attack(enemy)enemy はどう渡すの?

依存関係とは何か

「依存関係」という言葉は難しく聞こえますが、意味はシンプルです。

「このクラスがあのクラスを使っている」という関係

Player player = new Player();

このコード1行で「Game クラスは Player クラスに依存している」という関係が生まれます。Player クラスが存在しなければ、このコードはエラーになります。


依存関係の図

Game クラス
    └── Player クラスを使う(依存)
    └── Enemy クラスを使う(依存)
Player クラス
    └── Enemy クラスを引数で受け取る(依存)

図で見ると分かりますが、Game が中心で、PlayerEnemy を使っています。


正しい作成手順

複数クラスが出てきたときの対処は、第4回で学んだ「空クラス戦略」と同じです。

ステップ①:使う側のクラスを先に書く

class Game
{
    void Start()
    {
        Player player = new Player();
        Enemy enemy = new Enemy();
        player.Attack(enemy);
    }
}

まずこれを書きます。当然 PlayerEnemy がないのでエラーが出ます。


ステップ②:空のクラスを作る

class Player
{
}
class Enemy
{
}

これで new Player()new Enemy() のエラーは消えます。


ステップ③:必要なメソッドを追加する

player.Attack(enemy) のエラーが出ます。PlayerAttack メソッドを追加します。

class Player
{
    public void Attack(Enemy target)
    {
    }
}

引数に Enemy 型を受け取るようにします。これで player.Attack(enemy) のエラーも消えます。


ステップ④:中身を実装する

エラーが消えたら中身を書きます。

class Player
{
    public void Attack(Enemy target)
    {
        Console.WriteLine("Player attacks Enemy!");
    }
}
class Enemy
{
    public int hp = 100;
    public void TakeDamage(int damage)
    {
        hp -= damage;
        Console.WriteLine($"Enemy HP: {hp}");
    }
}

複数クラスをどこに書くか

「別のファイルに分けるべき?」と悩む人が多いです。

学習中は同じファイルに書いて構いません

// Program.cs に全部書いていい(学習中は)
class Game
{
    void Start()
    {
        Player player = new Player();
        Enemy enemy = new Enemy();
        player.Attack(enemy);
    }
}
class Player
{
    public void Attack(Enemy target)
    {
        Console.WriteLine("攻撃!");
    }
}
class Enemy
{
    public int hp = 100;
}

ファイルを分けるのは、コードが大きくなって管理しにくくなってからです。


参照の作り方:3パターン

複数クラスの参照には主に3つのパターンがあります。

パターン 書き方 使いどころ
引数で渡す player.Attack(enemy) 一時的に使う場合
フィールドに持つ this.enemy = enemy ずっと参照する場合
中で生成する Enemy enemy = new Enemy() 自分で管理する場合

引数で渡すパターン(詳細)

最も理解しやすいパターンです。

class Player
{
    public void Attack(Enemy target)
    {
        // target は引数として受け取った Enemy オブジェクト
        Console.WriteLine($"{target} に攻撃!");
    }
}

呼び出す側:

Enemy enemy = new Enemy();
player.Attack(enemy);   // enemy を引数として渡す

フィールドに持つパターン(詳細)

継続的に参照する場合はフィールドに持ちます。

class Game
{
    Player player;   // フィールドとして持つ
    Enemy enemy;     // フィールドとして持つ
    void Start()
    {
        player = new Player();
        enemy = new Enemy();
    }
    void Update()
    {
        player.Attack(enemy);   // どこからでも参照できる
    }
}

まとめ

  • 複数クラスで止まる原因は「依存関係」が見えていないから
  • 使う側のクラスを先に書き、エラーが出たら空クラスを作る
  • 学習中は同じファイルに複数クラスを書いて構わない
  • 参照のパターンは「引数で渡す」「フィールドに持つ」「中で生成する」の3つ

次の記事

Unity でも全く同じことが起きます。ただし C# と違い、Unity には独自の「置き場所」という概念があります。

第6回:Unityで同じことが起きる理由:置き場所という考え方


シリーズ一覧

タイトル
ハブ C#の写経がつらい理由と、正しい学習手順【完全版】
第1回 なぜC#の写経はつらいのか:赤い波線の正体
第2回 正しいC#の書き方:namespace→class→methodの順番
第3回 プロパティはいつ書くべきか:最後でOKな理由
第4回 エラーが出たらどうするか:その場で潰す技術
第5回(この記事) 複数クラスで止まる理由:依存関係を理解する
第6回 Unityで同じことが起きる理由:置き場所という考え方
第7回 プログラミングは構造で考える:写経を超えた先にあるもの