PostSharp 依存関係の読み込みの遅延

以下に示すコードは、おそらく複数回書いたものです。 そして、より可能性の高いもの-数十回。 これは通常、アプリケーションにデータを提供するリポジトリを使用する必要がある場合に記述されます。 時間があまりなく、急いでいる場合、これは何かを必要なときにだけメモリにロードするが、それより早くではない(たとえば、シリアル化サブシステムの呼び出しを伴う保存操作、ただし、それ以前は必要ありませんでした)。 そして実際、このコードは一方では同じであり、他方では複数回記述しなければならないことが判明しました。

原則として、私と多くのプログラマーは、このような問題を解決するためにこの場所でIoCコンテナーを使用することを好みます。 ただし、これは、特に使用するライブラリ(WinForms、WebForms、...)に依存性注入がない場合にプログラムを作成する場合、必ずしもそれほど簡単ではありません。 PostSharpを使用せずにこの問題を解決する理由を見てみましょう。より多くの時間を費やし、より多くの作業を行うことになります。





それで、どのくらいの頻度でこのコードに出くわしましたか:



private IProductRepository _productRepository;

private IProductRepository ProductRepository

{

get

{

if (_productRepository == null )

{

_productRepository = new ProductRepository();

}

return _productRepository;

}

}



* This source code was highlighted with Source Code Highlighter .








プロパティのタイプはIPropertyRepositoryですが、このプロパティの取得メソッドはまだProductRepositoryのタイプに関連付けられています。 「new ProductRepository()」を「new RevisedProductRepository()」または「new ProductRepository(2011)」に変更する必要がある場合は、ボイラープレートコードのすべての場所を調べて更新する必要があります。 もちろん、Find-> Replace、Copy-> Pasteを実行するために、すぐに手を差し伸べるでしょう...しかし、この状況は皆さんになじみがありますよね? まず、交換に多くの時間を費やします。次に、チームのほぼすべてのメンバーがこれに対処する必要があります(定期的な交換が必要な場合)。 また、誰もがすでにIoCコンテナを積極的に使用しているため、この例は誇張されていると思われるかもしれません。 しかし、私を信じて、そのような例は非常に一般的です! とにかく、IoCコンテナーを使用して、次のコードを取得します。



private IProductRepository _productRepository;

private IProductRepository ProductRepository

{

get

{

if (_productRepository == null )

{

_productRepository = ObjectFactory.GetInstance<IProductRepository>();

}

return _productRepository;

}

}



* This source code was highlighted with Source Code Highlighter .








ご覧のとおり、すべてのObjectFactoryが新しいインスタンスの作成を処理します。 クラスの新しいインスタンスを作成し、コンストラクターにパラメーターを追加し、オブジェクトの作成中に他の操作を実行する必要がある場合、これはプログラムの他の部分に影響を与えることなく1か所で実行できます(「ObjectFactory」-StructureMapの静的クラスに注意します。ただし、一番好きなIoCコンテナを絶対に使用してください)。 これでコードの見栄えが良くなり、開発チームは実装についてあまり心配する必要はなくなりました。インターフェイスについてだけです。 これは依存関係の本当の反転です。なぜなら、 これで、クラスは実装に依存せず、インターフェイスにのみ依存します。

コードはずっときれいになりましたが、アプリケーション全体で見つかる「null」および遅延初期化のチェックが含まれています(もちろん、遅延読み込みを頻繁に使用する場合)。 また、同じIoCコンテナを変更することは、それが私たちに合わなくなった場合、それほど簡単ではありません。 そして、ある時点でnullのチェックが不十分であると判断した場合(たとえば、プログラムがマルチタスクになった場合)、再びコード全体を調べ、変更、変更、変更する必要があります...最後にPostSharpを使用してこの問題を解決する方法を見てみましょう:



[LoadDependency] private IProductRepository _productRepository;



[ Serializable ]

public sealed class LoadDependencyAttribute : LocationInterceptionAspect

{

public override void OnGetValue(LocationInterceptionArgs args)

{

args.ProceedGetValue(); // fetches the field and populates the args.Value

if (args.Value == null )

{

var locationType = args.Location.LocationType;

var instantiation = ObjectFactory.GetInstance(locationType);



if (instantiation != null )

{

args.SetNewValue(instantiation);

}

args.ProceedGetValue();

}

}

}



* This source code was highlighted with Source Code Highlighter .








これで、依存関係を使用する宣言的なアプローチができました。 「LoadDependency」属性でフィールドをマークするだけで、最初の呼び出し中にIoCコンテナーによって初期化されます(最初の行を参照)。 したがって、レイジーローディング、nullチェック、スレッドセーフ、およびその他の必要な変更を使用する各プロパティの定型コードを絶えず記述することから身を守り、各プロパティを個別に記述します。 これで、すべてのアクションが1つのクラスになります。

非常にシンプルで小さい(19行のみ)アスペクトを使用して、結果のコードを12行から1行に圧縮しました。 多くの無意味な繰り返しコード( DRY )を削除し、ハードな依存関係をクリーンなインターフェース( DIP )に置き換えました。 独自のクラスに遅延読み込みを配置し、( SRP )で依存関係を解決します。 したがって、依存関係の数を最小限に減らします。

この非常に単純な例は、実際に私の日々の開発に非常に役立ち、非常に簡単になります。

ここで、すべての依存関係がコンパイル段階で既知であると仮定しましょう。 また、アプリケーションの実行中に依存関係が変化しないと仮定します。 この場合、Service Locatorは不要になりました。 コンパイル時に依存関係を解決し、一部のフィールドに型を保存するために、LocationInterceptionAspectのCompileTimeInitializeメソッドをオーバーライドできます。 次に、アプリケーションの実行中に、Activator.CreateInstanceを使用して目的のオブジェクトを作成できます。



[ Serializable ]

public sealed class LoadDependencyAttribute : LocationInterceptionAspect

{

private Type _type;



public override bool CompileTimeValidate(PostSharp.Reflection.LocationInfo locationInfo)

{

_type = DependencyMap.GetConcreteType(locationInfo.LocationType);

if (_type == null )

{

Message.Write(SeverityType.Error, "002" ,

"A concrete type was not found for {0}.{1}" ,

locationInfo.DeclaringType, locationInfo.Name);

return false ;

}

return true ;

}



public override void OnGetValue(LocationInterceptionArgs args)

{

args.ProceedGetValue();

if (args.Value == null )

{

form.LogListBox.Items.Add( "Instantiating UserService" );

args.SetNewValue(Activator.CreateInstance(_type));

args.ProceedGetValue();

}

}

}



* This source code was highlighted with Source Code Highlighter .








タイプを設定していない場合、アプリケーション段階ではなくコンパイル段階でエラーが発生します。これは非常に便利です。 PostSharpが非常に強力なツールであることを既に理解し始めていることを願っています。 もちろん、私はこの方法を必要とするあらゆる場所で使用することに興味はありません。 ただし、多くの状況で役立つと確信しています。

結果のアスペクトを改善するもう1つのオプションは、コンパイル時にアスペクトが正しく使用されていることを確認することです。 たとえば、フィールドがインターフェイスでない場合、そのフィールドにLoadDependencyアスペクトを使用することは意味がありません。 というのは、依存関係がハードコードされており、すべてを再度変更する必要があるからです! :)それでは、追加のチェックをいくつか追加しましょう。



public override bool CompileTimeValidate(PostSharp.Reflection.LocationInfo locationInfo)

{

if (!locationInfo.LocationType.IsInterface)

{

Message.Write(SeverityType.Error, "001" ,

"LoadDependency can only be used on Interfaces in {0}.{1}" ,

locationInfo.DeclaringType, locationInfo.Name);

return false ;

}



_type = DependencyMap.GetConcreteType(locationInfo.LocationType);

if (_type == null )

{

Message.Write(SeverityType.Error, "002" ,

"A concrete type was not found for {0}.{1}" ,

locationInfo.DeclaringType, locationInfo.Name);

return false ;

}

return true ;

}



* This source code was highlighted with Source Code Highlighter .








コンパイルエラーを取得するには、インターフェイスではなく、任意のタイプのフィールドに「LoadDependency」属性を設定します。







そのため、IoCコンテナーと組み合わせたシンプルな側面により、ハードな依存関係と定型コードを取り除き、アプリケーションの保守、テスト、およびソースコードの読み取りを容易にしました。 そして今、同じくらい重要なこととして、私たちのアプリケーションはSOLIDの原則に完全に準拠しています。 これで、開発段階とテスト段階だけでなく、サポート段階でも多くの時間を節約できます。



その他の翻訳とリンク:




All Articles