読書記録 C#実践開発手法 第1部-第2章

第2章は依存関係と階層化でした。
今回の読書記録では、個人的に勉強になったと感じた依存関係についてまとめておきます。

クラス間の依存関係

不適切な依存関係を持つソフトウェアの保守は大変になります。
一部の変更が意図しない箇所に影響したり、別のクラスやDLLを使用する際に余分なリファクタリングが必要になるからです。
まずは不適切な例を示します。

public class ControllerClass
{
    private readonly ServiceClass service;
   
    public ControllerClass()
    {
         this.service = new ServiceClass();
    }

    public void ChangePassword(Guid userID, string NewPass)
    {
        var userRepo = new UserRepository();
        var user = userRepo.GetByID(userID);
        this.service.ChangeUserPassword(user, newPass);
    }
}

この例のように、使用したいクラスControllerClassの中でServiceClassとUserRepositoryという二つのクラスが使用されています。
要するにControllerClassが二つのクラスに依存しています。
この状態では、ControllerClassを使うときにはこの二つのクラスが必ず必要になるため、単体テストや移植が難しくなってしまっています。
この根本的な原因は、ControllerClassが二つのクラスを直接生成しているためであります。

この問題を解決するために、今回は依存性の注入(DI)を適用します。
全体的な流れとしては、二つのクラスそれぞれに適切なインタフェースを設計し、ControllerClassは設計したインタフェースに依存するようにします。
リファクタリングしたコードが次の通りです。

public class ControllerClass
{
    //ServiceClassはIserviceClassインタフェースを持つ
    private readonly IServiceClass service;

    public ControllerClass(IServiceClass ser)
    {
        if(ser == null)
              //何らかの例外処理
        this.service = ser;
    }

    public void ChangePassword(Guid userID, string NewPass)
    {
       //UserRepositoryはIServiceClassに含まれるようになった
        this.service.ChangeUserPassword(userID,NewPass);
    }
}

このようにすることで、ServiceClassとUserRepositoryの実装が変わっても、インタフェース仕様さえ守っていれば
ControllerClassから問題なく使用できます。
また、コンストラクタ内でnullチェックすることで、以降の処理で例外を危惧するコストを抑えることもできます。
このように、IServiceClassに対する依存性を注入されることで変更に強いクラスになります。

アセンブリ間の依存関係

クラス間の依存関係では、あるクラスが別のクラスに依存するときには、実体に依存するのではなくインタフェースに依存する
ほうが良い、ということについて述べました。
これをアセンブリレベルで実現するには、インタフェースと実装を別のアセンブリにすることで問題の解決を図ります。
この方法の利点として

  • 実体に直接アクセスできなくなるため不適切な生成(安直なnew構文)が使えない
  • 別の依存関係を注入するとき一つのアセンブリが膨張することを防ぐ

の2点があります。

インタフェースと実体が同じアセンブリにある悪いパターンを「Entourageパターン」、別である良いパターンを「Stairwayパターン」と呼ぶそうです。

以上になります。
次回の更新は1/27を予定しております。