最終更新日 : 2003/4/16
asato <asato@ncfreak.com>
一度限りの Decorator
「静的 Decorator」は、実行時における上の二つの特徴を持っていませんが、コンパイル時には 2 番目の特徴を持つと考えられ、また、1 番目の特徴に関しては、1 枚の装飾に制限されない (何度でも重ねて装飾できる) と考えられます。
一方、「オブジェクト Decorator」は、実行時における 2 番目の特徴に加え、1 番目の特徴に関しては、何度 (何重にでも) でも装飾することができます。ただし、そのための実装は、やや複雑になります。
以下の「一度限りの Decorator」の実装では、この二つの特徴をもつ Decorator パターンの実装として (この実装を Decorator パターンと見なすかどうかは無視して)、いくつかの実装例を考えます。各実装例ごとに、細かいところでの様々な利点や欠点があります。
実装上の考慮と選択肢
GoF の Decorator パターンの実装においても ConcreteComponent の役割を持つオブジェクトは、直接は decorator オブジェクトのことを知りません。逆に、decorator オブジェクトは、concrete component オブジェクトへの参照を保持しています。
とはいえ、ConcreteComponent クラスは、Decorator パターンの構成要素して参加しています。つまり、Decorator パターン実現のための一部として考えられているわけです。というのも、一般的に ConcreteComponent クラスは、Component interface を実装することが意図されているためです。したがって、そのようなアプリケーション固有の役割を与えられることは、クラスの再利用性を妨げるかもしれません。
実装
実装 - その 1
役割 | 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 ]考察: この段階での実装からは、以下のような欠点が見られます:
String getString()
) だけしか advice 内で呼ぶことができない (advice 内の proceed()
を通して本来ののメソッドを呼び出している)。
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 : 複数回の装飾の禁止
GoF の Decorator パターンが複数回の装飾を可能としているため、本質的には、異なる Dectorator オブジェクト (あるいは、同じ Decorator あったとしても) による複数回の装飾ができることは悪いものではありません (実際、AspectJ を使った Decorator パターンの実装の 1 つである「オブジェクト 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
以下は、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 メソッド
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 メソッド
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$compdp_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 の排除
以下では、アプローチの 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 の排除 : 失敗編
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 は、まったく同じコードです。そのため、一見、このコードの重複をなくすことは容易に見えるかもしれません。しかし、この重複したコードを重複してないコードにすることは非常に困難です。
以下では、実際に、著者がこの重複をなくそうとして 失敗した いくつかのアプローチを紹介します。
抽象アスペクトに移動させる
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 {第一の問題点は、変更後のコードはコンパイルできないことです。というのも static な isDecorated メソッドは AbstractDecorator アスペクトからは、呼び出せない (定義されていない) ためです。したがって、コンパイルを通るようにするために、static な isDecorated メソッドを AbstractDecorator アスペクトに定義する必要があります:protected pointcut isDecorated() :if ( isDecorated( thisJoinPoint.getTarget() ) );String around() : getString() { return "*** " + proceed() + " ***"; } }
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 : 無限ループ
問題点は 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 : 完成
役割 | GoF | この実装 |
Component | interface | なし |
ConcreteComponent | Component interface を実装するクラス | クラス |
Decorator | Component interface を実装する抽象クラス | アスペクト |
ConcreteDecorator | Decorator を継承するクラス | アスペクト |
なし | なし | アスペクト |
「表 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
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