これから始めるSpring.NET その5

前回 までで、Spring.NETの基本的な機能について説明しました。
他にも様々な機能が用意されていますが、基本的に使用するのはobject要素とproperty要素の二つだけです。これだけを見るとSpring.NETというのはコードの中のnew宣言とプロパティ呼び出しを設定ファイルに切り離しただけのものに見えます。実際、その通りです。

ここまで読んで頂いた方のほとんどは「これだけの役割ではSpring.NETを導入するメリットは大してないんじゃね?」「( ・_ゝ・)ツマンネ」などと感じるのでなないでしょうか?
確かに、これではクラスを追加したり、プロパティを追加したりする度*1に、いちいちXMLの設定をいじらなくてはならないというコストが、オブジェクト間の依存関係を切り離す事によって得られる単体テストのし易さや修正コストの軽減などを上回ることになるでしょう。

では、そうまでしてなぜSpring.NETを使えと言うのでしょうか?それには、欠点を補って余りある利点があるからです。ここからはその事について説明していきます。

まず、私が考えるSpring.NETの利点とは次の三つ*2になります。

  1. 設定のインジェクション
  2. 宣言的インジェクション
  3. デザインパターンのより良い実装

設定のインジェクション

これはSpring.NETがインジェクションできるのは何もオブジェクトだけでは無いということです。

それなりのアプリケーションであれば、アプリケーションの設定を設定ファイルに分離する事があるでしょう。.NETの場合は通常、アプリケーション構成ファイルのappSettings要素を使用すると思います。

アプリケーション構成ファイルの例
<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <appSettings>
        <add key="file.encoding" value="Shift-JIS" />
    </appSettings>
</configuration>

この設定にプログラムからアクセスするコードは以下のようになります。

string fileEncoding = ConfigurationManager.AppSettings["file.encoding"]

使い方自体は簡単ですが、実際にこのコードを記述するとなると結構面倒くさいことになりますし、そもそもこのようなコードがあちこちに記述されるのは避けたいところでしょう。

では、どのようにしてこの状況を避ければいいのでしょうか?
とりあえず、前回作成したContentsManagerクラスを以下のように変更してみます。

ContentsManager.cs
public class ContentsManager : IContentsManager {
    private string fileEncoding;
    
    public ContentsManager() {
        fileEncoding = ConfigurationManager.AppSettings["file.encoding"];
    }

    public void Save(string contents, string fileName) {
        using(var sw = new StreamWriter(fileName, false, Encoding.GetEncoding(fileEncoding))) {
            sw.Write(contents);
        }
    }
}

Saveメソッドでファイルを作成する際に指定するエンコード名をアプリケーション構成ファイルから読み込むようにしました。

このようにしてしまうと、このクラスはアプリケーション構成ファイルがある環境でないと動作しないという問題があります。
この問題を解決するには以下のようにfileEncodingフィールドに外部からアクセスするためのプロパティを定義すればいいでしょう。

ContentsManager.cs
public class ContentsManager : IContentsManager {
    private string fileEncoding;
    public string FileEncoding {
        get { return fileEncoding; }
        set { fileEncoding = value; }
    }
    public ContentsManager() { }

    public void Save(string contents, string fileName) {
        using(var sw = new StreamWriter(fileName, false, Encoding.GetEncoding(FileEncoding))) {
            sw.Write(contents);
        }
    }
}

もちろんコンストラクタからは、アプリケーション構成ファイルにアクセスするコードを取り除いておきます。

こうなると、アプリケーション構成ファイルから読み取った設定をContentsManagerのFileEncodingプロパティに割り当てる必要が出てきます。それを実現するための機能がSpring.NETには用意されているのです。

その機能を利用するためにはまず、オブジェクト定義ファイルに以下のobject定義を追加します。

obects.config
<object name="propertyPlaceholderConfigurer"
    type="Spring.Objects.Factory.Config.PropertyPlaceholderConfigurer, Spring.Core">
    <property name="ConfigSections" value="appSettings" />
</object>

Spring.Objects.Factory.Config.PropertyPlaceholderConfigurer という型を指定し、ConfigSections プロパティに構成セクション名を指定しています。

あとは、オブジェクト定義ファイルのcontentsManagerのobject定義に以下のプロパティ設定を追加します。

objects.config
<object name="contentsManager" type="SpringSample.IO.ContentsManager, SpringSample">
    <property name="FileEncoding" value="${file.encoding}" />
</object>

value属性に${file.encoding}という見慣れないものを指定しています。これは「${}」で囲んだキー名の値をアプリケーション構成ファイルのappSettings要素から取得する事を意味しています。

こうすることで、オブジェクトのプロパティにアプリケーション構成ファイルの設定をインジェクションすることができます。

この機能を使うことで、アプリケーションの設定(オブジェクトのプロパティ)がいったいどこから割り当てられたかという事をコードから完全に隠蔽することができます。この事は設定ファイルの形式の柔軟性を上げることに繋がります。

さらに重要なことはこの機能はいくらでもカスタマイズすることができるので、設定を読みに行く場所はアプリケーション構成ファイルだけに限定されないということです*3

まずはこれが一つ目の利点です。

*1:他にもクラス名が変わったり、プロパティ名が変わった時もです

*2:今、私が思いつく限りなので、これからも増えていくと思います

*3:カスタマイズの方法についてもそのうち書くかもしれません