おーみんブログ

C#, ASP.NET Core, Unityが大好きです。

RealProxyクラスによるアスペクト指向プログラミングに入門してみた。

はじめに

先日デザインパターンProxyパターンを勉強したところ、お客様先(現在常駐している現場)の上司が記事を見てくださって「.NET FrameworkならRealProxy, .NET CoreならDispatchProxyを調べるとさらに勉強になりますよ~!」とアドバイスをいただいたので早速RealProxyから学んでみました。

アスペクト指向とRealProxy

RealProxyについて知るために、まずはアスペクト指向というワードについて学ぶ必要があります。 Microsoftの以下の記事が分かりやすかったので引用させていただきます。

docs.microsoft.com

適切に設計されたアプリケーションは独立した複数の層に分けられ、各層が異なる処理を担当し、必要以上にやり取りを行いません。疎結合型の保守しやすいアプリケーションを設計しているのに、開発も中盤に差し掛かったところでアーキテクチャにうまく適合しない次のような要件が提示されたとします。

  • データベースへの書き込みを行う前にデータを検証する必要がある。
  • 重要な操作には監査やログ記録が必要である。
  • 操作に問題がないかをチェックするためにデバッグ ログを管理する必要がある。
  • 一部の処理のパフォーマンスを測定して、パフォーマンスが目的の範囲内に収まっているかどうかを確認する必要がある。

これらの要件はどれも、多くの作業が必要です。そのうえ、コードの重複が生じます。同じコードをシステムの多くの箇所に追加しなければならず、DRY (don’t repeat yourself: 同じことを繰り返さない) の原則に反し、保守が難しくなります。要件の変更は、プログラムの大幅な変更につながります。アプリケーションにこのような修正を加える必要があると、個人的には「複数の箇所で繰り返されるコードをコンパイラが自動的に追加できないはなぜだろう」、あるいは「[このメソッドにログ記録を追加する] オプションがあったらよいのに」と思います。

このようなことを実現するのがアスペクト指向プログラミング (AOP) です。AOP は、一般的なコードと、アスペクト (オブジェクトや層の境界を横断する機能) を分離します。たとえば、アプリケーション ログはどのアプリケーション層にも結び付けられません。プログラム全体に適用され、どこにでも存在します。このようなものを「横断的な懸念事項」と呼びます。

AOP とは「横断的な懸念事項の分離を可能にすることによって、モジュール性を高めることを目的としたプログラミング パラダイム」です (Wikipedia からの引用、英語)。AOP はシステムの複数箇所で行われる機能を取り出し、アプリケーションの中核から分離します。そして、このような懸念事項の分離を進めながら、コードの重複や結合を取り除きます。

クラス間を横断するようなオブジェクト指向だけでは対処できないような処理をしたいときにアスペクト指向という考え方が必要なのですね。 そのような処理にたいしてデザインパターンのProxyパターン等がマッチするのですが、RealProxyクラスはさらにProxyの基本機能を提供してくれるクラスなのです。

サンプルコード

以下はRealProxyを用いてSample処理の処理前と処理後にログを入力するサンプルコードです。

class Program
{
    static void Main()
    {
        LogProxy logProxy = new LogProxy(new Sample());
        var sample = logProxy.GetTransparentProxy() as Sample;
        sample.SampleMethod();
    }
}

public class Sample : MarshalByRefObject
{
    public void SampleMethod() => Console.WriteLine($"{nameof(SampleMethod)}処理");
}

public class LogProxy : RealProxy
{
    private Sample _sample;
    public LogProxy(Sample sample) : base(typeof(Sample)) => _sample = sample;
    public override IMessage Invoke(IMessage msg)
    {
        var methodCall = msg as IMethodCallMessage;
        // SampleMethod()情報が入ってきます
        var methodInfo = methodCall.MethodBase as MethodInfo;
        Console.WriteLine($"これから{methodCall.MethodName}処理に入ります。");
        // SampleMethod()実行
        var result = methodInfo.Invoke(_sample, methodCall.InArgs);
        Console.WriteLine($"{methodCall.MethodName}処理が終わりました。");
        return new ReturnMessage(result, null, 0, methodCall.LogicalCallContext, methodCall);
    }
}
これからSampleMethod処理に入ります。
SampleMethod処理
SampleMethod処理が終わりました。

LogProxyのInvokeメソッドでSampleMethodの処理前、処理後のログを実装していることが分かります。 ここでログ処理をまとめつつ、LogProxyを使いまわすことで最初にMicrosoftドキュメントからの引用文に記載されていた以下の内容が満たせそうです。

これらの要件はどれも、多くの作業が必要です。そのうえ、コードの重複が生じます。同じコードをシステムの多くの箇所に追加しなければならず、DRY (don’t repeat yourself: 同じことを繰り返さない) の原則に反し、保守が難しくなります。要件の変更は、プログラムの大幅な変更につながります。アプリケーションにこのような修正を加える必要があると、個人的には「複数の箇所で繰り返されるコードをコンパイラが自動的に追加できないはなぜだろう」、あるいは「[このメソッドにログ記録を追加する] オプションがあったらよいのに」と思います。

おわりに

今回のサンプルコード実装で何となくAOPの雰囲気は分かりましたが、正直なところ、まだまだ曖昧な感じは否めません(;´∀`) 結構難しいですが個人的にはもっと知りたいなという欲求が出てきたので改めて勉強をしていこうと思います。 より深く理解したら記事に追記していこう~~\(^o^)/