Skip to content

第 9 章: SOLID 原則とモジュール設計

9.1 はじめに

前章ではデザインパターンを適用して設計を改善しました。この章では SOLID 原則 の観点から設計を評価し、namespace によるモジュール分割 でコードの責務を明確にします。

9.2 SOLID 原則の適用

単一責任の原則(SRP)

namespace 責務
FizzBuzz.Domain.Type FizzBuzz タイプの定義と生成ルール
FizzBuzz.Domain.Model 値オブジェクトとコレクション
FizzBuzz.Application コマンドの実行と結果の出力

各クラスが単一の責任を持つように設計されています。FizzBuzzType01 は通常の FizzBuzz ロジックのみ、FizzBuzzValue は値の保持と等価性のみを担当します。

開放閉鎖の原則(OCP)

IFizzBuzzType インターフェースを実装する新しいクラスを追加するだけで、既存のコードを変更せずに新しいタイプを追加できます。

// 既存コードを変更せずに新しいタイプを追加
public class FizzBuzzType04 : IFizzBuzzType
{
    public FizzBuzzValue Generate(int number)
    {
        // 新しいルールの実装
        return new FizzBuzzValue(number, number.ToString());
    }
}

リスコフの置換の原則(LSP)

IFizzBuzzType を実装するすべてのクラスは、IFizzBuzzType 型として交換可能です。

IFizzBuzzType type = new FizzBuzzType01(); // どの実装でも動作する
IFizzBuzzType type = new FizzBuzzType02(); // 交換可能

インターフェース分離の原則(ISP)

IFizzBuzzType インターフェースは Generate メソッドのみを定義しており、不必要なメソッドを強制しません。

public interface IFizzBuzzType
{
    FizzBuzzValue Generate(int number); // 必要最小限のメソッド
}

依存性逆転の原則(DIP)

FizzBuzzListCommand は具体的な FizzBuzzType01 ではなく IFizzBuzzType インターフェースに依存しています。

public class FizzBuzzListCommand : IFizzBuzzCommand
{
    private readonly IFizzBuzzType _type; // 抽象に依存

    public FizzBuzzListCommand(IFizzBuzzType type) // コンストラクタインジェクション
    {
        _type = type;
    }
}

9.3 モジュール分割

ディレクトリ構成

FizzBuzz/
├── FizzBuzz.cs                       (公開 API)
├── FizzBuzz.csproj
├── Application/
│   ├── IFizzBuzzCommand.cs
│   ├── FizzBuzzValueCommand.cs
│   └── FizzBuzzListCommand.cs
└── Domain/
    ├── Model/
    │   ├── FizzBuzzValue.cs
    │   └── FizzBuzzList.cs
    └── Type/
        ├── IFizzBuzzType.cs
        ├── FizzBuzzType01.cs
        ├── FizzBuzzType02.cs
        ├── FizzBuzzType03.cs
        ├── FizzBuzzTypeName.cs
        └── FizzBuzzTypeFactory.cs

C# の namespace システム

C# の namespace はモジュールを論理的にグループ化し、using で参照します。

// FizzBuzz/Domain/Type/IFizzBuzzType.cs
namespace FizzBuzz.Domain.Type;

using FizzBuzz.Domain.Model;

public interface IFizzBuzzType
{
    FizzBuzzValue Generate(int number);
}
// FizzBuzz/Application/FizzBuzzListCommand.cs
namespace FizzBuzz.Application;

using FizzBuzz.Domain.Model;
using FizzBuzz.Domain.Type;

public class FizzBuzzListCommand : IFizzBuzzCommand
{
    // ...
}

Rust の mod + pub によるモジュールシステムに対して、C# では namespace + using でモジュール間の依存関係を表現します。Java の package + import に近い感覚です。

FizzBuzzTypeName enum

タイプの名前を enum で型安全に定義します。

namespace FizzBuzz.Domain.Type;

public enum FizzBuzzTypeName
{
    Standard = 1,
    NumberOnly = 2,
    FizzBuzzOnly = 3
}

C# の enum は整数値に紐づく列挙型です。Rust の enum ほど強力ではありませんが、switch 式と組み合わせることで型安全なディスパッチが可能です。

9.4 依存関係

Application → Domain.Type
Application → Domain.Model
Domain.Type → Domain.Model
  • ApplicationDomain に依存
  • Domain.TypeDomain.Model に依存(FizzBuzzValue を生成)
  • 逆方向の依存は存在しない(単方向依存)

9.5 まとめ

この章では以下を実現しました。

原則 適用内容
SRP Type / Model / Application の責務分離
OCP interface による拡張ポイント
LSP IFizzBuzzType による交換可能性
ISP 最小限のインターフェース定義
DIP コンストラクタインジェクションによる依存の抽象化
namespace 責務 含まれる型
Domain.Type FizzBuzz 生成ルール IFizzBuzzType, Type01-03, Factory, TypeName
Domain.Model 値オブジェクト FizzBuzzValue, FizzBuzzList
Application コマンド実行 IFizzBuzzCommand, ValueCommand, ListCommand

第 3 部を通じて、C# の interface、class、namespace を使ったオブジェクト指向的な設計を学びました。次の第 4 部では、C# の関数型プログラミング機能(LINQ、デリゲート、パターンマッチ)を活用します。