LoginSignup
3
4

More than 5 years have passed since last update.

TypeScriptでAOPを自作してみた

Last updated at Posted at 2017-08-20

はじめに

TypeScript(ES5)開発で規模が大きくなってくるとAOP(Aspect Oriented Programming)が
使いたいと思う場面があります。
TypeScript(JavaScript)でもすでにAOPを行うためのプラグインなど公開されていますが
今回勉強もかねて自作してみました。

AOPのコード

Aspectクラスの各メソッドを呼ぶことでターゲットとするインスタンスのメソッドにAOPを行います。
ポイントはターゲットのメソッドを、それをラップしたメソッドですり替えることです。

メソッドの概要は以下の通りです。

変数名 説明
Before ターゲットの前に処理を挟み込みます
After ターゲットの後に処理を挟み込みます
Around ターゲットそのものを処理で挟み込みます

なお、引数は以下の通りとなります。

変数名 説明
target ターゲットとなるクラスのインスタンスです
methodName ターゲットとなるクラスのメソッド名です
execution 挟み込むメソッドを渡します。引数にJointPointを受け取るメソッドを指定します
TypeScript

class JoinPoint {

    private target: any;
    private method: any;
    private methodName: string;
    public arguments: IArguments;
    public canceled: boolean = false;
    public result: any;

    public constructor(target: any, method: any, methodName: string, args: IArguments) {
        this.target = target;
        this.method = method;
        this.methodName = methodName;
        this.arguments = args;
    }

    public proceed(): any {
        return this.method.apply(this.target, this.arguments);
    }
}

class Aspect {
    public static Before(target: any, methodName: string, execution: any): void {
        let method = target[methodName];
        target[methodName] = function () {
            let point = new JoinPoint(this, method, methodName, arguments)
            execution.apply(null, [point]);
            if (point.canceled) {
                return point.result;
            } else {
                return method.apply(target, arguments);
            }
        };
    }

    public static After(target: any, methodName: string, execution: any): void {
        let method = target[methodName];
        target[methodName] = function () {
            let point = new JoinPoint(this, method, methodName, arguments)
            point.result = method.apply(target, arguments);
            return execution.apply(null, [point]);
        };
    }

    public static Around(target: any, methodName: string, execution: any): void {
        let method = target[methodName];
        target[methodName] = function () {
            let point = new JoinPoint(this, method, methodName, arguments)
            return execution.apply(null, [point]);
        };
    }
}

AOPを使用する

使用する側のコードは以下の通りです。
JsFiddleで動作確認いただけます。
ポイントとしては

  • Beforeにて、にターゲット処理をキャンセルしたい場合はJointPoint.canceledをtrueにすることで キャンセルできるようにしました。
  • Aroundにて、ターゲット処理を実行するにはJointPoint.proceed()を実行します。
TypeScript
class Greeting {
    public Say(value: string): void {
        console.log(value);
    }
}

class AopTest {
    public static Test(): void {
        let gr = new Greeting();

        // 処理前に挟み込む
        //Aspect.Before(gr, "Say", () => {
        //    console.log("Before Say");
        //});

        // 処理後に挟み込む
        //Aspect.After(gr, "Say", () => {
        //    console.log("After Say");
        //});

        // 処理前後に挟み込む
        Aspect.Around(gr, "Say", (point: JoinPoint) => {
            console.log("Before");
            let result = point.proceed();
            console.log("After");
            return result;
        });

        // 処理を実行
        gr.Say("Hello");
    }
}

さいごに

メソッドをラップする部分に関してはObject.definePropertyによるラップもできそうでしたが
今回は単純にメソッドそのものを上書きするように実装しました。

また、本コードは実験的なコードであり実際に使い込むと、使いにくい部分や不具合などがあるかもしれません。

3
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
4