はじめに
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によるラップもできそうでしたが
今回は単純にメソッドそのものを上書きするように実装しました。
また、本コードは実験的なコードであり実際に使い込むと、使いにくい部分や不具合などがあるかもしれません。