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

最終更新日 : 2003/4/16
asato <asato@ncfreak.com>

index > decorator

一度限りの Decorator

AspectJ を使った Decorator パターンの実装として、このテクニックでは、その特徴として以下のもつものを考えます: また、このテクニックは「静的 Decorator」と「オブジェクト Decorator」の中間に位置すると見なせます。

「静的 Decorator」は、実行時における上の二つの特徴を持っていませんが、コンパイル時には 2 番目の特徴を持つと考えられ、また、1 番目の特徴に関しては、1 枚の装飾に制限されない (何度でも重ねて装飾できる) と考えられます。

一方、「オブジェクト Decorator」は、実行時における 2 番目の特徴に加え、1 番目の特徴に関しては、何度 (何重にでも) でも装飾することができます。ただし、そのための実装は、やや複雑になります。

以下の「一度限りの Decorator」の実装では、この二つの特徴をもつ Decorator パターンの実装として (この実装を Decorator パターンと見なすかどうかは無視して)、いくつかの実装例を考えます。各実装例ごとに、細かいところでの様々な利点や欠点があります。

実装上の考慮と選択肢

この文章で紹介しているすべてのテクニックを通して暗黙に強要していることの 1 つに装飾対象となるクラス (Component) は、直接は、どの装飾するクラスとの関係を持たない、ということがあります。また、Component クラスは、装飾を意識した実装であってはならない、ともしています。つまり、Component クラスのコードの中に、たとえば「もし装飾されているのなら、こっちのコードを実行する」というものや、あるいは「もし、decorator オブジェクトへの参照を持っているのなら、decorator オブジェクトのコードのメソッドを呼び出す」といった、条件文的なコードを埋め込まない、ということです。

GoF の Decorator パターンの実装においても ConcreteComponent の役割を持つオブジェクトは、直接は decorator オブジェクトのことを知りません。逆に、decorator オブジェクトは、concrete component オブジェクトへの参照を保持しています。

とはいえ、ConcreteComponent クラスは、Decorator パターンの構成要素して参加しています。つまり、Decorator パターン実現のための一部として考えられているわけです。というのも、一般的に ConcreteComponent クラスは、Component interface を実装することが意図されているためです。したがって、そのようなアプリケーション固有の役割を与えられることは、クラスの再利用性を妨げるかもしれません。

実装

実装 - その 1

この実装のアプローチでは、装飾対象となるオブジェクトを装飾する役割を持ったものとして、クラスではなく、アスペクトを使います。
表 1. GoF との対応
役割 GoF この実装
Component interface なし
ConcreteComponent Component interface を実装するクラス クラス
Decorator Component interface を実装する抽象クラス なし
ConcreteDecorator Decorator を継承するクラス アスペクト

「表 1」は、この実装における初期段階での、GoF Decorator パターンとこの実装での構成要素の対応を示しています。

段階 - その 1 : 何が問題なのか

public class StringComponent {

	private String str;

	public StringComponent(String str) {
		this.str = str;
	}

	public String getString() {
		return str;
	}
}
public class Client {

	public static void main(String[] args) {

		StringComponent comp = new StringComponent("aaa");

		System.out.println( comp.getString() ); // aaa

		ConcreteDecoratorA.decorate(comp);

		System.out.println( comp.getString() ); // [ aaa ]
	}
}
public aspect ConcreteDecoratorA pertarget( component() ) {

	private pointcut component() : target(StringComponent);

	private boolean isDecorated;

	public static void decorate(StringComponent comp) {
		ConcreteDecoratorA.aspectOf(comp).isDecorated = true;
	}

	private static boolean isDecorated(Object comp) {
		return ConcreteDecoratorA.aspectOf( comp ).isDecorated;
	}

	private pointcut getString() :
		call(String StringComponent.getString() ) &&
		if ( isDecorated( thisJoinPoint.getTarget() ) );

	String around() : getString() {
		return "[[[ " + proceed() + " ]]]";
	}
}
aaa
[ aaa ]
考察: この段階での実装からは、以下のような欠点が見られます: 以下では、下の二つの問題について詳しく解説します。

2 番目の問題であるコードの重複については、たとえば、具体的には、以下のようなものです (違いが見られる部分については太字にしています):

public aspect ConcreteDecoratorA pertarget( component() ) {

	private pointcut component() : target(StringComponent);

	private boolean isDecorated;

	public static void decorate(StringComponent comp) {
		ConcreteDecoratorA.aspectOf(comp).isDecorated = true;
	}

	private static boolean isDecorated(Object comp) {
		return ConcreteDecoratorA.aspectOf( comp ).isDecorated;
	}

	private pointcut getString() :
		call(String StringComponent.getString() ) &&
		if ( isDecorated( thisJoinPoint.getTarget() ) );

	String around() : getString() {
		return "[[[ " + proceed() + " ]]]";
	}
}
public aspect ConcreteDecoratorB pertarget( component() ) {

	private pointcut component() : target(StringComponent);

	private boolean isDecorated;

	public static void decorate(StringComponent comp) {
		ConcreteDecoratorB.aspectOf(comp).isDecorated = true;
	}

	private static boolean isDecorated(Object comp) {
		return ConcreteDecoratorB.aspectOf( comp ).isDecorated;
	}

	private pointcut getString() :
		call(String StringComponent.getString() ) &&
		if ( isDecorated( thisJoinPoint.getTarget() ) );

	String around() : getString() {
		return "*** " + proceed() + " ***";
	}
}
一般にコードの重複は望ましくないため、なんらかの解決策が必要です。

3 番目の、中途半端な複数回の装飾のサポートに関する問題については、具体的には以下のようなものです:

public class StringComponent {
	// ... 変更なし
}
public aspect ConcreteDecoratorA pertarget( component() ) {
	// ... 変更なし
}
public aspect ConcreteDecoratorB pertarget( component() ) {
	// ... 変更なし
}
public class Client {

	public static void main(String[] args) {

		StringComponent comp = new StringComponent("aaa");

		System.out.println( comp.getString() ); // aaa


		ConcreteDecoratorA.decorate(comp);

		System.out.println( comp.getString() ); // [[[ aaa ]]]


		ConcreteDecoratorB.decorate(comp);

		System.out.println( comp.getString() ); // *** [[[ aaa ]]] ***
	}
}
一見、問題なく 見えます が:
public class Client {

	public static void main(String[] args) {

		StringComponent comp = new StringComponent("aaa");

		System.out.println( comp.getString() ); // aaa


		ConcreteDecoratorB.decorate(comp);

		System.out.println( comp.getString() ); // *** aaa ***


		ConcreteDecoratorA.decorate(comp);

		System.out.println( comp.getString() ); // [[[ *** aaa *** ]]] を期待するが
		                                        // 実際には *** [[[ aaa ]]] ***

	}
}
さらには:
public class Client {

	public static void main(String[] args) {

		StringComponent comp = new StringComponent("aaa");

		System.out.println( comp.getString() ); // aaa


		ConcreteDecoratorA.decorate(comp);

		System.out.println( comp.getString() ); // [[[ aaa ]]]


		ConcreteDecoratorA.decorate(comp);

		System.out.println( comp.getString() ); // [[[ [[[ aaa ]]] ]]] を期待するが
		                                        // 実際には [[[ aaa ]]]
	}
}
以上、見てきたように、今の段階では主に 3 つの問題があることが分かりました。次の段階の実装では、まずは、1 番目の問題 (振る舞いを変えようとしているメソッドだけしか advice 内で呼ぶことができない) のみを解決することにします。

段階 - その 2 : オブジェクトへのアクセス

装飾対象となるオブジェクトへの参照を得る方法には、二つの方法が考えられます: 前者の実装は、容易です:
public class StringComponent {

	private String str;

	public StringComponent(String str) {
		this.str = str;
	}

	public void setString(String str) { // advice 内で使用する
		this.str = str;
	}

	public String getString() {
		return str;
	}
}
public class Client {

	public static void main(String[] args) {

		StringComponent comp = new StringComponent("aaa");

		System.out.println( comp.getString() ); // aaa

		ConcreteDecoratorA.decorate(comp);

		System.out.println( comp.getString() ); // [[[ xxx ]]]
	}
}
public aspect ConcreteDecoratorA pertarget( component() ) {

	private pointcut component() : target(StringComponent);

	private boolean isDecorated;

	public static void decorate(StringComponent comp) {
		ConcreteDecoratorA.aspectOf(comp).isDecorated = true;
	}

	private static boolean isDecorated(Object comp) {
		return ConcreteDecoratorA.aspectOf( comp ).isDecorated;
	}

	private pointcut getString(StringComponent comp) :
		target(comp) &&
		call(String StringComponent.getString() ) &&
		if ( isDecorated( comp ) );

	String around(StringComponent comp) : getString(comp) {
		comp.setString("xxx");
		return "[[[ " + proceed(comp) + " ]]]";
	}
}
aaa
[[[ xxx ]]]
後者も同様に簡単です:
public class StringComponent { // 上と変更なし

	private String str;

	public StringComponent(String str) {
		this.str = str;
	}

	public void setString(String str) { // advice 内で使用する
		this.str = str;
	}

	public String getString() {
		return str;
	}
}
public class Client { // 上と変更なし

	public static void main(String[] args) {

		StringComponent comp = new StringComponent("aaa");

		System.out.println( comp.getString() ); // aaa

		ConcreteDecoratorA.decorate(comp);

		System.out.println( comp.getString() ); // [[[ xxx ]]]
	}
}
public aspect ConcreteDecoratorA pertarget( component() ) {

	pointcut component() : target(StringComponent);

	private StringComponent comp;

	public static void decorate(StringComponent comp) {
		ConcreteDecoratorA.aspectOf(comp).comp = comp;
	}

	private static boolean isDecorated(Object comp) {
		return ConcreteDecoratorA.aspectOf( comp ).comp != null;
	}

	pointcut getString() :
		call(String StringComponent.getString() ) &&
		if ( isDecorated( thisJoinPoint.getTarget() ) );

	String around() : getString() {
		comp.setString("xxx");
		return "[[[ " + proceed() + " ]]]";
	}
}
aaa
[[[ xxx ]]]

そう、前者も後者もやっていることは対して変わりません。では、どちらのアプローチがよりベターなのでしょうか? 個人的には、後者のアプローチを好みます。というのも around advice にパラメータを書く必要がないためです。

この例のようなシンプルなケース (振る舞いを変えたいメソッドが 1 つ) では、それほど違いは表せませんが、もし 1 つ以上のメソッドの振る舞いを変えたい場合には、より違いが明らかになるかもしれません。以下は、そのような例です:

public class StringComponent {

	private String str;

	public StringComponent(String str) {
		this.str = str;
	}

	public String getString() {
		return str;
	}
	public String getString2() {
		return str;
	}
}
public class Client {

	public static void main(String[] args) {

		StringComponent comp = new StringComponent("aaa");

		System.out.println( comp.getString() );  // aaa
		System.out.println( comp.getString2() ); // aaa


		ConcreteDecoratorA.decorate(comp);

		System.out.println( comp.getString() );  // [[[ aaa ]]]
		System.out.println( comp.getString2() ); // [ aaa ]
	}
}
前者に関しては、以下のようなアスペクトの実装になります:
public aspect ConcreteDecoratorA pertarget( component() ) {

	// ... 前と一緒

	private pointcut comp(StringComponent comp) :
		target(comp) && if ( isDecorated( comp ) );


	private pointcut getString(StringComponent comp) :
		comp(comp) &&
		call(String StringComponent.getString() );

	private pointcut getString2(StringComponent comp) :
		comp(comp) &&
		call(String StringComponent.getString2() );


	String around(StringComponent comp) : getString(comp) {
		return "[[[ " + proceed(comp) + " ]]]";
	}

	String around(StringComponent comp) : getString2(comp) {
		return "[ " + proceed(comp) + " ]";
	}
}
後者に関しては、以下のようなアスペクトの実装になります:
public aspect ConcreteDecoratorA pertarget( component() ) {

	// ... 前と一緒

	private pointcut isDecorated() :
		if ( isDecorated( thisJoinPoint.getTarget() ) );


	pointcut getString() :
		isDecorated() && 
		call(String StringComponent.getString() );

	pointcut getString2() :
		isDecorated() && 
		call(String StringComponent.getString2() );


	String around() : getString() {
		return "[[[ " + proceed() + " ]]]";
	}

	String around() : getString2() {
		return "[ " + proceed() + " ]";
	}
}

恐らく、後者の方が読みやすいかと思います。

これで、1 番目の問題については解決できました。しかし、前の段階での問題でもあった二つの問題がまだ残っています:

次の段階の実装では、これら二つの問題解決に焦点を当てたコードを紹介します。

段階 - その 3 : 複数回の装飾の禁止

この段階では、段階その 1 で示したように、この実装では意図していない振る舞いである、何重にも可能なオブジェクトの振る舞いの変更の禁止についてを扱います。

GoF の Decorator パターンが複数回の装飾を可能としているため、本質的には、異なる Dectorator オブジェクト (あるいは、同じ Decorator あったとしても) による複数回の装飾ができることは悪いものではありません (実際、AspectJ を使った Decorator パターンの実装の 1 つである「オブジェクト Decorator」では、複数回の装飾を許しています) 。しかし、この実装では、それを許さないようにします。

複数回の装飾ができないようにする理由としては、以下のものが考えられます:

前者はともかく、後者の理由については、できるかぎり議論をシンプルにするためです。実際、実践的には、ここで紹介している Decorator パターンの実装が役に立つとは限りません。むしろ、ここで紹介している Decorator パターンの実装は、複数回の装飾を可能にする Decorator パターンの実装に到達するための中継点であると考えています。

以下は、複数の装飾ができないようにするための実装です。今までのクラスとアスペクトに加え、新しい StringComponentDecorator というアスペクトが加えられていることに注意してください:

public class StringComponent { // 今までと特に変更なし

	private String str;

	public StringComponent(String str) {
		this.str = str;
	}

	public String getString() {
		return str;
	}
}
public class Client {

	public static void main(String[] args) {

		StringComponent comp = new StringComponent("aaa");

		System.out.println( comp.getString() ); // aaa


		ConcreteDecoratorA.decorate(comp);

		System.out.println( comp.getString() ); // [[[ aaa ]]]


		ConcreteDecoratorB.decorate(comp);

		System.out.println( comp.getString() ); // *** aaa ***
	}
}
public aspect StringComponentDecorator {

	declare parents : ConcreteDecoratorA implements Decorator;
	declare parents : ConcreteDecoratorB implements Decorator;


	public interface Decorator { }

	public Decorator StringComponent.decorator;

	public StringComponent Decorator.comp;
}
public aspect ConcreteDecoratorA pertarget( component() ) {

	private pointcut component() : target(StringComponent);

	public static void decorate(StringComponent comp) {

		if (comp.decorator != null) {
			comp.decorator.comp = null;
		}
		comp.decorator = ConcreteDecoratorA.aspectOf(comp);
		comp.decorator.comp = comp;
	}

	private static boolean isDecorated(Object comp) {
		return ConcreteDecoratorA.aspectOf( comp ).comp != null;
	}

	private pointcut getString() :
		call(String StringComponent.getString() ) &&
		if ( isDecorated( thisJoinPoint.getTarget() ) );

	String around() : getString() {
		return "[[[ " + proceed() + " ]]]";
	}
}
public aspect ConcreteDecoratorB pertarget( component() ) {

	private pointcut component() : target(StringComponent);

	public static void decorate(StringComponent comp) {
		
		if (comp.decorator != null) {
			comp.decorator.comp = null;
		}
		comp.decorator = ConcreteDecoratorB.aspectOf(comp);
		comp.decorator.comp = comp;
	}

	private static boolean isDecorated(Object comp) {
		return ConcreteDecoratorB.aspectOf( comp ).comp != null;
	}

	private pointcut getString() :
		call(String StringComponent.getString() ) &&
		if ( isDecorated( thisJoinPoint.getTarget() ) );

	String around() : getString() {
		return "*** " + proceed() + " ***";
	}
}
ConcreteDecoratorA と ConcreteDecoratorB との間に多少のコードの重複が見られますが、この段階での目標であった複数回の装飾を許さない、という目標は達成できました。

次の段階では、コードの重複を排除することを目的とします。

段階 - その 4 : コードの重複の排除 - その 1

この段階では、前の段階での実装を元にして、さらなる完璧な実装を目指します。前段階での問題点は、各 ConcreteDecorator の間で見られる似たようなコードの重複の存在でした。したがって、この段階では、その重複をなくすことを目的とします。

以下は、ConcreteDecoratorA と ConcreteDecoratorB との間で重複していたコードを、StringComponentDecorator アスペクトに移したあとの実装です:

public class StringComponent { 
	// ... 今までと特に変更なし
}
public class Client {
	// ... 今までと特に変更なし
}
public aspect StringComponentDecorator {

	pointcut component() : target(StringComponent);

	declare parents : ConcreteDecoratorA implements Decorator;
	declare parents : ConcreteDecoratorB implements Decorator;


	public interface Decorator { }

	private Decorator StringComponent.decorator;

	public StringComponent Decorator.comp;

	public static void decorate(Decorator decorator, StringComponent comp) {
		if (comp.decorator != null) {
			comp.decorator.comp = null;
		}
		comp.decorator = decorator;
		comp.decorator.comp = comp;
	}

	pointcut getString() :
		call(String StringComponent.getString() );
}
public aspect ConcreteDecoratorA pertarget( StringComponentDecorator.component() ) {

	public static void decorate(StringComponent comp) {
		StringComponentDecorator.decorate( ConcreteDecoratorA.aspectOf(comp), comp);
	}

	private static boolean isDecorated(Object comp) {
		return ConcreteDecoratorA.aspectOf( comp ).comp != null;
	}

	private pointcut isDecorated() : 
		if ( isDecorated( thisJoinPoint.getTarget() ) );

	private pointcut getString() :
		isDecorated() && StringComponentDecorator.getString();

	String around() : getString() {
		return "[[[ " + proceed() + " ]]]";
	}
}
public aspect ConcreteDecoratorB pertarget( StringComponentDecorator.component() ) {

	public static void decorate(StringComponent comp) {
		StringComponentDecorator.decorate( ConcreteDecoratorB.aspectOf(comp), comp);
	}

	private static boolean isDecorated(Object comp) {
		return ConcreteDecoratorB.aspectOf( comp ).comp != null;
	}

	private pointcut isDecorated() : 
		if ( isDecorated( thisJoinPoint.getTarget() ) );

	private pointcut getString() :
		isDecorated() && StringComponentDecorator.getString();


	String around() : getString() {
		return "*** " + proceed() + " ***";
	}
}

段階 - その 5 : コードの重複の排除 - その 2 : decorate メソッド

そう、これ以上のコードの重複排除は不可能なのでしょうか? 不可能ではありませんが、コードが複雑になります。以下は、そのようなコードです。このコードでは、主に各 ConcreteDecorator の static な decorate メソッド間でのコードの重複 (些細な重複ですが) を排除することに焦点を当てています:
import java.lang.reflect.*;

import org.aspectj.lang.*;


public aspect StringComponentDecorator {

	pointcut component() : target(StringComponent);

	declare parents : ConcreteDecoratorA implements Decorator;
	declare parents : ConcreteDecoratorB implements Decorator;


	public static void ConcreteDecoratorA.decorate(StringComponent comp) { }
	public static void ConcreteDecoratorB.decorate(StringComponent comp) { }


	public interface Decorator { }

	private Decorator StringComponent.decorator;

	public StringComponent Decorator.comp;

	public static void decorate(Decorator decorator, StringComponent comp) {

		if (comp.decorator != null) {
			comp.decorator.comp = null;
		}

		comp.decorator = decorator;
		comp.decorator.comp = comp;
	}

	pointcut getString() :
		call(String StringComponent.getString() );

	void around() : call( static void Decorator+.decorate(StringComponent) ) {

		Object comp      = thisJoinPoint.getArgs()[0];
		Object decorator = getDecoratorObject(thisJoinPoint);

		try {
			getDecorateMethod().invoke(null, new Object[] { decorator, comp });

		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
	
	private Method getDecorateMethod() {

		Class clazz = StringComponentDecorator.class;

		Class[] paramTypes =
			new Class[] { Decorator.class, StringComponent.class };

		try {
			return clazz.getMethod("decorate",  paramTypes);

		} catch (NoSuchMethodException e) {
			throw new RuntimeException(e);
		}
	}
	
	private static Object getDecoratorObject(JoinPoint jp) {

		Class clazz = jp.getSignature().getDeclaringType();

		try {
			Method method =
				clazz.getMethod("aspectOf", new Class[] { Object.class });

			return method.invoke(null, jp.getArgs() );

		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
}
public aspect ConcreteDecoratorA pertarget( StringComponentDecorator.component() ) {

	private static boolean isDecorated(Object comp) {
		return ConcreteDecoratorA.aspectOf( comp ).comp != null;
	}

	private pointcut isDecorated() : 
		if ( isDecorated( thisJoinPoint.getTarget() ) );

	private pointcut getString() :
		isDecorated() && StringComponentDecorator.getString();

	String around() : getString() {
		return "[[[ " + proceed() + " ]]]";
	}
}
public aspect ConcreteDecoratorB pertarget( StringComponentDecorator.component() ) {

	private static boolean isDecorated(Object comp) {
		return ConcreteDecoratorB.aspectOf( comp ).comp != null;
	}

	private pointcut isDecorated() : 
		if ( isDecorated( thisJoinPoint.getTarget() ) );

	private pointcut getString() :
		isDecorated() && StringComponentDecorator.getString();


	String around() : getString() {
		return "*** " + proceed() + " ***";
	}
}
コードから見れるように、これは、シンプルな解決策とは言えませんが、1 つのアプローチです。

段階 - その 6 : コードの重複の排除 - その 3 : isDecorated メソッド

続いての 重複したコード削除のターゲットになるのは、各 ConcreteDecorator のそれぞれで定義されている static なメソッドである isDecorated です。

decorate メソッドの時と同じく、シンプルな解決策とは呼べませんが、以下はそのための実装です:

package dp.decorator.one_time;

import java.util.*;
import java.lang.reflect.*;

import org.aspectj.lang.*;

public aspect StringComponentDecorator {

	pointcut component() : target(StringComponent);

	declare parents : ConcreteDecoratorA implements Decorator;
	declare parents : ConcreteDecoratorB implements Decorator;


	public static void ConcreteDecoratorA.decorate(StringComponent comp) { }
	public static void ConcreteDecoratorB.decorate(StringComponent comp) { }

	public static boolean ConcreteDecoratorA.isDecorated(Object comp) {
		return false;
	}
	public static boolean ConcreteDecoratorB.isDecorated(Object comp) {
		return false;
	}


	public interface Decorator { }

	public StringComponent Decorator.comp;

	public StringComponent Decorator.getComponent() {
		return comp;
	}

	private Decorator StringComponent.decorator;

	public static void decorate(Decorator decorator, StringComponent comp) {
		// ... 前と一緒。
	}

	pointcut getString() :
		call(String StringComponent.getString() );

	void around() : call( static void Decorator+.decorate(StringComponent) ) {
		// ... 前と一緒
	}
	
	private Method getDecorateMethod() {
		// ... 前と一緒
	}
	
	private static Object getDecoratorObject(JoinPoint jp) {
		// ... 前と一緒
	}

	boolean around() : execution(boolean Decorator+.isDecorated(Object) ) {

		Object decorator = getDecoratorObject(thisJoinPoint);

		try {
			Method method =
				decorator.getClass().getMethod("getComponent", new Class[] {} );
			Object comp = method.invoke(decorator, null );

			return comp != null;

		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
}
public aspect ConcreteDecoratorA pertarget( StringComponentDecorator.component() ) {

	private pointcut isDecorated() : 
		if ( isDecorated( thisJoinPoint.getTarget() ) );

	private pointcut getString() :
		isDecorated() && StringComponentDecorator.getString();

	String around() : getString() {
		return "[[[ " + proceed() + " ]]]";
	}
}
public aspect ConcreteDecoratorB pertarget( StringComponentDecorator.component() ) {

	private pointcut isDecorated() : 
		if ( isDecorated( thisJoinPoint.getTarget() ) );

	private pointcut getString() :
		isDecorated() && StringComponentDecorator.getString();


	String around() : getString() {
		return "*** " + proceed() + " ***";
	}
}
このコードから見られる 1 つの気になるポイントは、わざわざ getComponent() メソッドを新たに追加している点です:
public aspect StringComponentDecorator {

	// ... 省略

	public StringComponent Decorator.getComponent() {
		return comp;
	}

	// ... 省略
}
このメソッドは、単に、各 ConcreteDecorator オブジェクトの装飾対象となっているオブジェクトを返すだけですが、疑問なのは、オブジェクトを取得するための手段として、フィールドを通してではなく、メソッドを通して、という点です。

この getComponent() メソッドと関係のあるコードは以下の部分です:

public aspect StringComponentDecorator {

	// ... 省略
	
	boolean around() : execution(boolean Decorator+.isDecorated(Object) ) {

		Object decorator = getDecoratorObject(thisJoinPoint);

		try {
			Method method =
				decorator.getClass().getMethod("getComponent", new Class[] {} );
			Object comp = method.invoke(decorator, null );

			return comp != null;

		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
}
この advice の目的は、特定の ConcreteDecorator オブジェクトが装飾対象となるオブジェクトへの参照を持っているかどうかをチェックすることです。

具体的には、以下のメソッドからの、コードの重複削除のためのアプローチでした:

public aspect ConcreteDecoratorA pertarget( StringComponentDecorator.component() ) {

	private static boolean isDecorated(Object comp) {
		return ConcreteDecoratorA.aspectOf( comp ).comp != null;
	}

	// ... 省略
}
つまり、フィールド (comp) が null かどうかをチェックするだけで目的は達成できるわけです。それなのに、なぜわざわざ新しいメソッド (getComponent()) を追加し、なぜ advice 内で、リフレクションを通して、そのフィールドの値にアクセスしようとしているのでしょうか?

理由は comp フィールドが Introduction を通して各 ConcreteDecorator クラスに導入されているためです:

public aspect StringComponentDecorator {

	// ... 省略

	public StringComponent Decorator.comp;

	// ... 省略
}
Introduction を通して新たに導入されたフィールドには、リフレクションを使っては素直にアクセスできません。たとえば、以下のようなコードでは NoSuchFieldException がスローされます:
public aspect StringComponentDecorator {

	// ... 省略

	boolean around() : execution(boolean Decorator+.isDecorated(Object) ) {

		Object decorator = getDecoratorObject(thisJoinPoint);

		try {
			// NoSuchFieldException をスロー
			Field field = decorator.getClass().getField("comp");
			return field.get(decorator) != null;

		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
}
問題なのは comp フィールドが各 ConcreteDecorator クラスに定義されていないのではなく、別のフィールド名で定義されてしまっていることです:
public aspect StringComponentDecorator {

	// ... 省略

	boolean around() : execution(boolean Decorator+.isDecorated(Object) ) {

		Object decorator = getDecoratorObject(thisJoinPoint);

		Field[] fields = decorator.getClass().getFields();
		
		for(int i = 0 ; i < fields.length ; i++) {
			System.out.println(fields[i].getName());
		}

		return false;
	}
advice を以上のように変更して実際のフィールド名を確かめてみると、以下のような結果が得られます (長いので途中で改行してあります):
ajc$interField$dp_decorator_one_time_StringComponentDecorator
$dp_decorator_one_time_StringComponentDecorator$Decorator$comp
dp_decorator_one_time は dp.decorator.one_time というパッケージ名のことであり、この実装で紹介したクラスやアスペクトは、このパッケージ内に存在しています。

そう、もし、この長いフィールド名を、comp フィールド名の変わりに使ったとするなら、うまくいくのでしょうか? 実際、NoSuchFieldException がスローされることなく、うまくいきます:

public aspect StringComponentDecorator {

	// ... 省略

	boolean around() : execution(boolean Decorator+.isDecorated(Object) ) {

		Object decorator = getDecoratorObject(thisJoinPoint);

		try {
			String name = "ajc$interField";
			name += "$dp_decorator_one_time_StringComponentDecorator";
			name += "$dp_decorator_one_time_StringComponentDecorator";
			name += "$Decorator$comp";

			Field field = decorator.getClass().getField(name);

			return field.get(decorator) != null;

		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
}
うまくいきますが、恐らく、別の手段を使った方が得策に思われます。したがって、この実装では、リフレクションを使ってフィールド (comp) にアクセスするよりも、新たにメソッド (getComponent()) を導入し、そのメソッドを通してフィールドにアクセスにすることにしました。

段階 - その 7 : コードの重複の排除 - その 4 : pointcut の排除

前回までのコードの重複の活動もあって、シンプルな解決策とは言えなくても、その目標は一応達成されました。しかし、コードの重複はまだ残っています。この段階では、 前回までの活動の結果として、最終的に、各 ConcreteDecorator 間で重複するコードとなってしまった pointcut である getString() をなくすことを目標とします。

以下では、アプローチの 1 つとして各 ConcreteDecorator 間で共通の抽象アスペクトである AbstractDecorator アスペクトを新たに追加することにより、コードの重複に対して挑みたいと思います。

以下は、そのための実装です:

public abstract aspect AbstractDecorator pertarget( StringComponentDecorator.component() ) {

	protected abstract pointcut isDecorated();

	protected pointcut getString() :
		isDecorated() && StringComponentDecorator.getString();
}
public aspect ConcreteDecoratorA extends AbstractDecorator {

	protected pointcut isDecorated() : 
		if ( isDecorated( thisJoinPoint.getTarget() ) );

	String around() : getString() {
		return "[[[ " + proceed() + " ]]]";
	}
}
public aspect ConcreteDecoratorB extends AbstractDecorator {

	protected pointcut isDecorated() : 
		if ( isDecorated( thisJoinPoint.getTarget() ) );

	String around() : getString() {
		return "*** " + proceed() + " ***";
	}
}
これで、コードの重複に対処するための実装は終了です。しかし、読者の方は isDecorated() pointcut がまだ ConcreteDecoratorA と ConcreteDecoratorB の間で重複して存在していることに気づいたかもしれません。もちろん、著者は、この重複をなくそうとトライしてみましたが、そうすることは思った以上に困難であることに気づきました。

次の段階では、このコード重複削除の問題を解決しようとしても 失敗する いくつかのアプローチを紹介します。

段階 - その 8 : コードの重複の排除 - その 5 : isDecorated pointcut の排除 : 失敗編

基本的な目標は、前の段階でまだなお残っているコードの重複をなくすことです。具体的には、以下の二つのアスペクト間に共通して存在する isDecorated() pointcut を、重複しないようすることです:
public aspect StringComponentDecorator { // 前回までと変化なし

	pointcut component() : target(StringComponent);

	declare parents : ConcreteDecoratorA implements Decorator;
	declare parents : ConcreteDecoratorB implements Decorator;


	public static void ConcreteDecoratorA.decorate(StringComponent comp) { }
	public static void ConcreteDecoratorB.decorate(StringComponent comp) { }

	public static boolean ConcreteDecoratorA.isDecorated(Object comp) {
		return false;
	}
	public static boolean ConcreteDecoratorB.isDecorated(Object comp) {
		return false;
	}

	public interface Decorator { }

	private Decorator StringComponent.decorator;

	public StringComponent Decorator.comp;


	public StringComponent Decorator.getComponent() {
		return comp;
	}


	public static void decorate(Decorator decorator, StringComponent comp) {

		if (comp.decorator != null) {
			comp.decorator.comp = null;
		}

		comp.decorator = decorator;
		comp.decorator.comp = comp;
	}

	pointcut getString() :
		call(String StringComponent.getString() );

	void around() : call( static void Decorator+.decorate(StringComponent) ) {

		Object comp      = thisJoinPoint.getArgs()[0];
		Object decorator = getDecoratorObject(thisJoinPoint);

		try {
			getDecorateMethod().invoke(null, new Object[] { decorator, comp });

		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
	
	private Method getDecorateMethod() {

		Class clazz = StringComponentDecorator.class;

		Class[] paramTypes =
			new Class[] { Decorator.class, StringComponent.class };

		try {
			return clazz.getMethod("decorate",  paramTypes);

		} catch (NoSuchMethodException e) {
			throw new RuntimeException(e);
		}
	}
	
	private static Object getDecoratorObject(JoinPoint jp) {

		Class clazz = jp.getSignature().getDeclaringType();

		try {
			Method method =
				clazz.getMethod("aspectOf", new Class[] { Object.class });

			return method.invoke(null, jp.getArgs() );

		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	boolean around() : execution(boolean Decorator+.isDecorated(Object) ) {

		Object decorator = getDecoratorObject(thisJoinPoint);

		try {
			Method method =
				decorator.getClass().getMethod("getComponent", new Class[] {} );
			Object comp = method.invoke(decorator, null );

			return comp != null;

		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
}
public abstract aspect AbstractDecorator pertarget( StringComponentDecorator.component() ) {

	protected abstract pointcut isDecorated();

	protected pointcut getString() :
		isDecorated() && StringComponentDecorator.getString();
}
public aspect ConcreteDecoratorA extends AbstractDecorator {

	protected pointcut isDecorated() : 
		if ( isDecorated( thisJoinPoint.getTarget() ) );

	String around() : getString() {
		return "[[[ " + proceed() + " ]]]";
	}
}
public aspect ConcreteDecoratorB extends AbstractDecorator {

	protected pointcut isDecorated() : 
		if ( isDecorated( thisJoinPoint.getTarget() ) );

	String around() : getString() {
		return "*** " + proceed() + " ***";
	}
}
コードから見られるように、両方のアスペクト間に存在する isDecorated pointcut は、まったく同じコードです。そのため、一見、このコードの重複をなくすことは容易に見えるかもしれません。しかし、この重複したコードを重複してないコードにすることは非常に困難です。

以下では、実際に、著者がこの重複をなくそうとして 失敗した いくつかのアプローチを紹介します。

抽象アスペクトに移動させる

最初のアプローチは、isDecorated pointcut が、共通の抽象アスペクト (AbstractDecorator) を継承したアスペクト間 (ConcreteDecoratorA と ConcreteDecoratorB) に存在していることに注目して、isDecorated pointcut をその抽象アスペクトに移動させることにより、コードの重複をなくそうとすることです。このような考え方は、従来の Java でのオブジェクトプログラミングでは大抵うまくいきます。しかし、この場合には失敗します:
public abstract aspect AbstractDecorator pertarget( StringComponentDecorator.component() ) {

	protected pointcut isDecorated() : 
		if ( isDecorated( thisJoinPoint.getTarget() ) );

	protected pointcut getString() :
		isDecorated() && StringComponentDecorator.getString();
}
public aspect ConcreteDecoratorA extends AbstractDecorator {

	protected pointcut isDecorated() :
		if ( isDecorated( thisJoinPoint.getTarget() ) );

	String around() : getString() {
		return "[[[ " + proceed() + " ]]]";
	}
}
public aspect ConcreteDecoratorB extends AbstractDecorator {

	protected pointcut isDecorated() : 
		if ( isDecorated( thisJoinPoint.getTarget() ) );

	String around() : getString() {
		return "*** " + proceed() + " ***";
	}
}
第一の問題点は、変更後のコードはコンパイルできないことです。というのも static な isDecorated メソッドは AbstractDecorator アスペクトからは、呼び出せない (定義されていない) ためです。したがって、コンパイルを通るようにするために、static な isDecorated メソッドを AbstractDecorator アスペクトに定義する必要があります:
public abstract aspect AbstractDecorator pertarget( StringComponentDecorator.component() ) {

	private static boolean isDecorated(Object comp) {
		return false;
	}

	protected pointcut isDecorated() : 
		if ( isDecorated( thisJoinPoint.getTarget() ) );

	protected pointcut getString() :
		isDecorated() && StringComponentDecorator.getString();
}
新たに定義された isDecorated メソッド内でどのようにして対象となるオブジェクトを装飾しているかどうかのチェックを行うのかについては、今のところは、考えないでおきます。

この変更により、コンパイルは通りますが、期待通りの振る舞いは得られません:

public class Client {

	public static void main(String[] args) {

		StringComponent comp = new StringComponent("aaa");

		System.out.println( comp.getString() ); // aaa

		ConcreteDecoratorA.decorate(comp);
		System.out.println( comp.getString() ); // aaa

		ConcreteDecoratorB.decorate(comp);
		System.out.println( comp.getString() ); // aaa
	}
}
このような期待していない振る舞いになる原因は AbstractDecorator の isDecorated メソッドが呼ばれているためです。

各 ConcreteDecoratorA と ConcreteDecoratorB のそれぞれも static な isDecorated メソッドを定義していますが、これらのメソッドは呼ばれません:

public aspect StringComponentDecorator {

	// ... その他の定義

	public static boolean ConcreteDecoratorA.isDecorated(Object comp) {
		return false;
	}
	public static boolean ConcreteDecoratorB.isDecorated(Object comp) {
		return false;
	}

	// ... その他の定義
}
isDecorated pointcut を、各 ConcreteDecorator から AbstractDecorator に移動させ、また、AbstractDecorator に isDecorated メソッドを定義する 前は 各 ConcreteDecorator の isDecorated メソッド 呼ばれていました。そのため、期待通りの振る舞いが得られました。しかし、変更後は、AbstractDecorator isDecorated メソッドが呼ばれています。これでは、うまくいきません。

変更前の実装が、期待通りの振る舞いをしていたのは pointcut とリフレクションをうまく組み合わせて使っていたためです。

コード的には、StringComponentDecorator アスペクトの以下の部分がそれにあたります:

public aspect StringComponentDecorator {

	// ... その他の実装

	boolean around() : execution(boolean Decorator+.isDecorated(Object) ) {

		Object decorator = getDecoratorObject(thisJoinPoint);

		try {
			Method method =
				decorator.getClass().getMethod("getComponent", new Class[] {} );
			Object comp = method.invoke(decorator, null );

			return comp != null;

		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
}
期待通りにいっていたことを説明するために、コードを変更前のコードに戻して考えてみます:
public abstract aspect AbstractDecorator pertarget( StringComponentDecorator.component() ) {

	protected abstract pointcut isDecorated();

	protected pointcut getString() :
		isDecorated() && StringComponentDecorator.getString();
}
public aspect ConcreteDecoratorA extends AbstractDecorator {

	protected pointcut isDecorated() : 
		if ( isDecorated( thisJoinPoint.getTarget() ) );

	String around() : getString() {
		return "[[[ " + proceed() + " ]]]";
	}
}
public aspect ConcreteDecoratorB extends AbstractDecorator {

	protected pointcut isDecorated() : 
		if ( isDecorated( thisJoinPoint.getTarget() ) );

	String around() : getString() {
		return "*** " + proceed() + " ***";
	}
}
また、説明のため StringComponentDecorator アスペクトにも変更を加えます:
public aspect StringComponentDecorator {

	// ... その他の実装

	boolean around() : execution(boolean Decorator+.isDecorated(Object) ) {

		System.out.println( thisJoinPoint.getSignature().getDeclaringType() );

		return false;
	}
}
このように変更したとして、振る舞いを確かめてみます:
public class Client {

	public static void main(String[] args) {

		StringComponent comp = new StringComponent("aaa");

		System.out.println( comp.getString() ); // aaa

		ConcreteDecoratorA.decorate(comp);
		System.out.println( comp.getString() ); // aaa

		ConcreteDecoratorB.decorate(comp);
		System.out.println( comp.getString() ); // aaa
	}
}
実行結果は、以下のようになります:
class dp.decorator.one_time.ConcreteDecoratorB
class dp.decorator.one_time.ConcreteDecoratorA
aaa
class dp.decorator.one_time.ConcreteDecoratorB
class dp.decorator.one_time.ConcreteDecoratorA
aaa
class dp.decorator.one_time.ConcreteDecoratorB
class dp.decorator.one_time.ConcreteDecoratorA
aaa
期待通りの振る舞いになっていない ("aaa" だけしか表示されていない) ことについては、無視してください。注目すべきは、クラス名の部分です。

getDeclaringType() メソッドから、現在の実行予定中の ConcreteDecorator クラスの名前が得られています。このクラス名は、この後、リフレクションを通して、間接的に ConcreteDecorator.aspectOf(target component) を呼び出し、装飾対象となる component オブジェクトに対応する concrete decorator アスペクトのインスタンスを得ています。

アスペクトのインスタンスを取得後、さらにリフレクションを使うことにより ConcreteDecorator.getComponent() メソッドを間接的に呼んでいます。その後、ConcreteDecorator アスペクトが装飾対象となるオブジェクトを実際に装飾しているかどうかをチェックします (getComponent() != null なら、装飾している)。

先ほど紹介したように、今説明したステップを表したコードが以下のようになります:

public aspect StringComponentDecorator {

	// ... その他の実装

	private static Object getDecoratorObject(JoinPoint jp) {

		Class clazz = jp.getSignature().getDeclaringType();

		try {
			Method method =
				clazz.getMethod("aspectOf", new Class[] { Object.class });

			return method.invoke(null, jp.getArgs() );

		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	boolean around() : execution(boolean Decorator+.isDecorated(Object) ) {

		Object decorator = getDecoratorObject(thisJoinPoint);

		try {
			Method method =
				decorator.getClass().getMethod("getComponent", new Class[] {} );
			Object comp = method.invoke(decorator, null );

			return comp != null;

		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
}

リストを利用する

public abstract aspect AbstractDecorator pertarget( StringComponentDecorator.component() ) {

	private static List list = new ArrayList();
	private static int index = 0;

	public AbstractDecorator() {

		if ( list.contains( this.getClass() ) == false) {
			list.add( this.getClass() );
		}
	}

	private static boolean isDecorated(Object obj) {

		StringComponent comp = (StringComponent)obj;

		if (comp.decorator == null) return false;

		index++;

		if ( index == list.size() ) {
			index = 0;
		}

		Object decoType = list.get(index);

		return decoType.equals( comp.decorator.getClass() );
	}

	protected pointcut isDecorated() :
		if ( isDecorated( thisJoinPoint.getTarget() ) );

	protected pointcut getString() :
		isDecorated() &&
		StringComponentDecorator.getString();
}
public aspect ConcreteDecoratorA extends AbstractDecorator {

	String around() : getString() {
		return "[[[ " + proceed() + " ]]]";
	}
}
public aspect ConcreteDecoratorB extends AbstractDecorator {

	String around() : getString() {
		return "*** " + proceed() + " ***";
	}
}
public aspect StringComponentDecorator {

	pointcut component() : target(StringComponent);

	declare parents : ConcreteDecoratorA implements Decorator;
	declare parents : ConcreteDecoratorB implements Decorator;


	public static void ConcreteDecoratorA.decorate(StringComponent comp) { }
	public static void ConcreteDecoratorB.decorate(StringComponent comp) { }

	public interface Decorator { }

	public Decorator StringComponent.decorator;

	public StringComponent Decorator.comp;

	public static void decorate(Decorator decorator, StringComponent comp) {

		if (comp.decorator != null) {
			comp.decorator.comp = null;
		}

		comp.decorator = decorator;
		comp.decorator.comp = comp;
	}

	pointcut getString() :
		call(String StringComponent.getString() );

	void around() : call( static void Decorator+.decorate(StringComponent) ) {

		Object comp      = thisJoinPoint.getArgs()[0];
		Object decorator = getDecoratorObject(thisJoinPoint);

		try {
			getDecorateMethod().invoke(null, new Object[] { decorator, comp });

		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
	
	private Method getDecorateMethod() {

		Class clazz = StringComponentDecorator.class;

		Class[] paramTypes =
			new Class[] { Decorator.class, StringComponent.class };

		try {
			return clazz.getMethod("decorate",  paramTypes);

		} catch (NoSuchMethodException e) {
			throw new RuntimeException(e);
		}
	}
	
	private static Object getDecoratorObject(JoinPoint jp) {

		Class clazz = jp.getSignature().getDeclaringType();

		try {
			Method method =
				clazz.getMethod("aspectOf", new Class[] { Object.class });

			return method.invoke( null, jp.getArgs() );

		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
}

段階 - その 9 : 無限ループ

これまでの数回の段階では、コードの重複削除、ということに重点を置いてきました。しかし、いつのまにか AspectJ プログラミングにおける最も遭遇しやすいかもしれない問題に出くわしてしまっています: 無限ループです。

問題点は advice 内で装飾対象となっているオブジェクトの、振る舞いを変えようとしているメソッドを呼び出すことにより明らかになります:

public aspect ConcreteDecoratorA extends AbstractDecorator {

	protected pointcut isDecorated() : 
		if ( isDecorated( thisJoinPoint.getTarget() ) );

	String around() : getString() {

		return "[[[ " + comp.getString() + " ]]]";
	}
}
問題なのは advice 内でのメソッドの呼び出し (この場合は comp.getString() ) 自体も pointcut の対象となってしまうことです。したがって、それを防ぐためにも、pointcut になんらかの制約 (「もし ConcreteDecoratorA アスペクト内でのメソッドの呼び出しではないなら」といった) を指定する必要があります。

この種の問題に対処するためには、典型的には within pointcut が使えます:

public aspect StringComponentDecorator {

	// ... 省略

	pointcut getString() :
		!within(Decorator+) && call(String StringComponent.getString() );

	// ... 省略
}

段階 - その 10 : 完成

「表 2」は最終段階での、GoF Decorator パターンとの構成要素の比較です。
表 2. GoF との対応
役割 GoF この実装
Component interface なし
ConcreteComponent Component interface を実装するクラス クラス
Decorator Component interface を実装する抽象クラス アスペクト
ConcreteDecorator Decorator を継承するクラス アスペクト
なし なし アスペクト

「表 3」は、この実装での構成要素です。
表 3.
役割 説明 種類
ConcreteComponent 振る舞いを変える対象となるクラス クラス StringComponent
AbstractDecorator 抽象アスペクト AbstractDecorator
ConcreteDecorator AbstractDecorator を継承するアスペクト アスペクト ConcreteDecoratorA (B)
ComponentAspect アスペクト StringComponentAspect

実装 - その 2

public class StringComponent {

	private String str;

	public StringComponent(String str) {
		this.str = str;
	}

	public String getString() {
		return str;
	}
}
public abstract aspect Decorator {

	private StringComponentDecorator StringComponent.decorator;

	protected static void decorate(StringComponent comp, StringComponentDecorator decorator) {
		comp.decorator = decorator;
		decorator.setComponent(comp);
	}

	protected pointcut getString(StringComponent comp) :
		target(comp) && 
		call(String StringComponent.getString() ) &&
		!within(Decorator+);


	String around(StringComponent comp) : getString(comp) {

		if ( comp.decorator == null) {
			return proceed(comp);
		} else {
			return comp.decorator.getString();
		}
	}
}
public abstract aspect StringComponentDecorator pertarget( component() ) {

	protected pointcut component() : target(StringComponent);

	protected StringComponent comp;

	public abstract String getString();

	public void setComponent(StringComponent comp) {
		this.comp = comp;
	}
}
public aspect ConcreteDecoratorA extends Decorator {

	public static void decorate(StringComponent comp) {
		decorate(comp, ConcreteDecoratorA.Decorator.aspectOf(comp) );
	}

	private static aspect Decorator extends StringComponentDecorator {

		public String getString() {
			return "[[[ " + comp.getString() + " ]]]";
		}
	}
}
public aspect ConcreteDecoratorB extends Decorator {

	public static void decorate(StringComponent comp) {
		decorate(comp, ConcreteDecoratorB.Decorator.aspectOf(comp) );
	}

	private static aspect Decorator extends StringComponentDecorator {

		public String getString() {
			return "*** " + comp.getString() + " ***";
		}
	}
}
public class Client {

	public static void main(String[] args) {

		StringComponent comp = new StringComponent("aaa");

		System.out.println( comp.getString() ); // aaa

		ConcreteDecoratorA.decorate(comp);

		System.out.println( comp.getString() ); // [[[ aaa ]]]

		ConcreteDecoratorB.decorate(comp);

		System.out.println( comp.getString() ); // *** aaa ***
	}
}

実装 - その 3

基本的には「実装 その 2」の変形。
public class StringComponent {

	private String str;

	public StringComponent(String str) {
		this.str = str;
	}

	public String getString() {
		return str;
	}
}
public abstract aspect Decorator pertarget( component() )  {

	protected pointcut component() : target(StringComponent);

	protected StringComponent comp;

	private Decorator StringComponent.decorator;

	protected static void decorate(StringComponent comp, Decorator decorator) {
		comp.decorator = decorator;
		decorator.comp = comp;
	}

	public abstract String getString();


	protected pointcut getString(StringComponent comp) :
		target(comp) && 
		call(String StringComponent.getString() ) &&
		!within(Decorator+) &&
		if (comp.decorator != null);


	String around(StringComponent comp) : getString(comp) {
		return comp.decorator.getString();
	}
}
public aspect ConcreteDecoratorA extends Decorator {

	public static void decorate(StringComponent comp) {
		decorate(comp, ConcreteDecoratorA.aspectOf(comp) );
	}

	public String getString() {
		return "[[[ " + comp.getString() + " ]]]";
	}
}
public aspect ConcreteDecoratorB extends Decorator {

	public static void decorate(StringComponent comp) {
		decorate(comp, ConcreteDecoratorB.aspectOf(comp) );
	}
	public String getString() {
		return "*** " + comp.getString() + " ***";
	}
}
public class Client {

	public static void main(String[] args) {

		StringComponent comp = new StringComponent("aaa");

		System.out.println( comp.getString() ); // aaa

		ConcreteDecoratorA.decorate(comp);

		System.out.println( comp.getString() ); // [[[ aaa ]]]

		ConcreteDecoratorB.decorate(comp);

		System.out.println( comp.getString() ); // *** aaa ***
	}
}

更新履歴

todo

index > decorator