TypeScriptでAOPを自作してみた

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

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

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

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

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

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


```java: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](https://jsfiddle.net/9yw09o69/)で動作確認いただけます。
ポイントとしては

- Beforeにて、にターゲット処理をキャンセルしたい場合はJointPoint.canceledをtrueにすることで
キャンセルできるようにしました。
- Aroundにて、ターゲット処理を実行するにはJointPoint.proceed()を実行します。

```Java: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*によるラップもできそうでしたが
今回は単純にメソッドそのものを上書きするように実装しました。

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