AspectJ を使ったデザインパターンの改善と支援 >
Decorator

最終更新日 : 2003/3/30
asato <asato@ncfreak.com>

index > decorator

静的 Decorator

Jan Hannemann 氏らによる AspectJ を使った Decorator パターンの実装 [1] は、advice をベースにしたものであり非常に静的なものです。一方 GoF の Decorator パターン使用の目的は、動的なものです。以下では、氏らの実装を「静的 Decorator」と呼ぶことにします。
advice-based decorator の方が名前的に良い?

実装

静的 Decorator の実装は、主に advice の適用を利用しています:
public class StringComponent {

	public void print(String s) {
		System.out.println(s);
	}
}
public aspect ConcreteDecoratorA {

	declare precedence: ConcreteDecoratorA, ConcreteDecoratorB;

	protected pointcut printCall(String s):
		call(public void StringComponent.print(String)) && args(s);

	void around(String s): printCall(s) {
		s = "*** " + s + " ***";
		proceed(s);
	}
}
public aspect ConcreteDecoratorB {

	protected pointcut printCall(String s):
		call(public void StringComponent.print(String)) && args(s);

	void around(String s): printCall(s) {
		s = "[ " + s + " ]";
		proceed(s);
	}
}
public class Client {

	public static void main(String[] args) {

		StringComponent comp = new StringComponent();
		comp.print("aaa"); // [ *** aaa *** ]
	}
}
[ *** aaa *** ]

疑問

前節の実装からは、以下のような疑問が現れます:

Decorator パターンであると言えるのか?

Decorator パターンに至る動機の 1 つは、動的にオブジェクトに責任を追加したい、というものからです。前節での実装から見られるように、静的 Decorator の実装は、その名の通り、静的です。AspectJ を使ってコンパイルした後には、GoF の Decorator パターンがそれをできるようには、動的にオブジェクトに責任を追加することはできません。

静的 Decorator が柔軟性を発揮できる場所は、コンパイル前に precedence 宣言を使うことにより、装飾の順番を変えることぐらいです:

public class StringComponent {
	// ... 変更なし
}
public aspect ConcreteDecoratorA {

	// 装飾の順番を変更
	declare precedence: ConcreteDecoratorB, ConcreteDecoratorA;

	// ... 変更なし
}
public aspect ConcreteDecoratorB {
	// ... 変更なし
}
public class Client {

	public static void main(String[] args) {

		StringComponent comp = new StringComponent();
		comp.print("aaa"); // *** [ aaa ] ***
	}
}
*** [ aaa ] ***
しかし、それに伴って次に思いつく疑問は、実践的にそのようなシチュエーションはありえるのか? というものです。

実践的に使えるのか?

「実装」の節でみたような実装では、まず思い浮かぶ疑問は「なぜわざわざ advice を使って静的にオブジェクトの振る舞いを変えようとするのか?」です。つまり、コンパイル時に静的に振る舞いを変えだけなのなら、わざわざ個別の振る舞いを各アスペクト (実装の節で言えば、ConcreteDecoratorA と ConcreteDecoratorB) に分離する理由は何ででしょうか? 以下のような実装では、いけなかったのでしょうか?
public class StringComponent {

	public void print(String s) {
		System.out.println("*** [ " + s + " ] ***");
	}
}
ここで注意する必要があるのは、まず、実装例自体が非常に単純なものである、ということです。したがって、わざわざアスペクトを導入するまでもない、という結論にも至りえます。

もう 1 つは、ConcreteComponent クラス (StringComponent) の作者と ConcreteDecorator (ConcreteDecoratorA と ConcreteDecoratorB) の作者が、同じであり、静的 Decorator を使うかどうかのどちらかをとらなければいけない、と仮定していることです。これは、起こりえる唯一の状況ではありません。

具体的には、以下のような状況が考えられます:

後者の場合、静的 Decorator が、GoF の Decorator に忠実かどうかに関係なく、それがたとえコンパイル時にでの責任の追加が選択肢としてできることであったとしても、ConcreteComponent (StringComponent) クラスの振る舞いをソースコードを変更することなく変えて使用したい、と望んでいる開発者にとっては、従来の Java のみでは取りえなかった選択肢の 1 つです。

状況例

A さん: ライブラリ開発者

package a_lib;

public class StringComponent {

	public void print(String s) {
		System.out.println(s);
	}
}
package a_lib;

public class Main {
	public static void main(String[] args) {
		StringComponent comp = new StringComponent();
		comp.print("aaa"); // aaa
	}
}
<project name="decorator" default="compile" >

	<target name="compile" >
		<mkdir dir="build" />

		<javac destdir="build" srcdir="src"/>

		<jar jarfile="a_lib.jar" basedir="build" />

	</target>

</project>
a_lib>java -cp a_lib.jar a_lib.Main
aaa
B さん: アプリケーション開発者
package b_app;

import a_lib.StringComponent;

public class Main {
	public static void main(String[] args) {
		StringComponent comp = new StringComponent();
		comp.print("aaa"); // *** [ aaa ] ***
	}
}
package b_app;

import a_lib.StringComponent;

public aspect ConcreteDecoratorA {

	declare precedence: ConcreteDecoratorB, ConcreteDecoratorA;

	protected pointcut printCall(String s):
		call(public void StringComponent.print(String)) && args(s);

	void around(String s): printCall(s) {
		s = "*** " + s + " ***";
		proceed(s);
	}
}
package b_app;

import a_lib.StringComponent;

public aspect ConcreteDecoratorB {

	protected pointcut printCall(String s):
		call(public void StringComponent.print(String)) && args(s);

	void around(String s): printCall(s) {
		s = "[ " + s + " ]";
		proceed(s);
	}
}
ajc -verbose -injars ../a_lib/a_lib.jar -sourceroots src -outjar b_app.jar
b_app>java -cp ../../lib/aspectjrt.jar;b_app.jar b_app.Main
*** [ aaa ] ***
考察

この例から見て取れることは、例えば、以下のものです:

重複の削除

1 つの方法は、ConcreteDecoratorA と ConcreteDecoratorB の間に共通の抽象アスペクトを導入することです:

package b_app;

import a_lib.StringComponent;

public abstract aspect AbstractDecorator {

	protected pointcut printCall(String s):
		call(public void StringComponent.print(String)) && args(s);
}
package b_app;

public aspect ConcreteDecoratorA extends AbstractDecorator {

	declare precedence: ConcreteDecoratorB, ConcreteDecoratorA;

	void around(String s): printCall(s) {
		s = "*** " + s + " ***";
		proceed(s);
	}
}
package b_app;

public aspect ConcreteDecoratorB extends AbstractDecorator {

	void around(String s): printCall(s) {
		s = "[ " + s + " ]";
		proceed(s);
	}
}
もう 1 つの方法は、アスペクトの継承を行わない方法です:
package b_app;

import a_lib.StringComponent;

public aspect StringComponentAspect {

	public pointcut printCall(String s):
		call(public void StringComponent.print(String)) && args(s);
}
package b_app;

public aspect ConcreteDecoratorA {

	declare precedence: ConcreteDecoratorB, ConcreteDecoratorA;

	void around(String s): StringComponentAspect.printCall(s) {
		s = "*** " + s + " ***";
		proceed(s);
	}
}
package b_app;

public aspect ConcreteDecoratorB {

	void around(String s): StringComponentAspect.printCall(s) {
		s = "[ " + s + " ]";
		proceed(s);
	}
}
その他の実装方法

場合によっては、以下のような実装も適切かもしれません:

package b_app;

import a_lib.StringComponent;

public aspect StringComponentDecorator {

	declare precedence: ConcreteDecoratorA, ConcreteDecoratorB;

	private pointcut printCall(String s):
		call(public void StringComponent.print(String)) && args(s);

	private static aspect ConcreteDecoratorA {

		void around(String s): printCall(s) {
			s = "*** " + s + " ***";
			proceed(s);
		}
	}
	private static aspect ConcreteDecoratorB {

		void around(String s): printCall(s) {
			s = "[ " + s + " ]";
			proceed(s);
		}
	}
}
あるいは単に:
package b_app;

import a_lib.StringComponent;

public aspect StringComponentDecorator {

	private pointcut printCall(String s):
		call(public void StringComponent.print(String)) && args(s);


	void around(String s): printCall(s) {
		s = "[ " + s + " ]";
		proceed(s);
	}

	void around(String s): printCall(s) {
		s = "*** " + s + " ***";
		proceed(s);
	}
}
ただし、この場合は、どの順番で advice が呼び出されるかによってオブジェクトの動作が変わりえます (実際に、この場合では、上から順に advice が実行されます)。

参考文献とリソース

更新履歴

todo

index > decorator