最終更新日 : 2003/4/1
asato <asato@ncfreak.com>
オブジェクトの同一性を意識した Decorator の実装
問題: GoF のデザインパターンでは、装飾されるオブジェクトと装飾された後のオブジェクトを、同じオブジェクトとして扱うことができない。
適用可能性:
結果 (利点):
例として、以下のプログラムを考える (各クラスの実装の詳細については後述のサンプルコードの節を参照)
package dp; import dp.decorator.*; public class Client { public static void main(String[] args) { // decorate1 メソッドを使って装飾 StringComponent comp1 = new ConcreteStringComponent("aaa"); StringComponent comp2 = decorate1(comp1); System.out.println( comp1.getString() ); // [ aaa ] System.out.println( comp2.getString() ); // [ aaa ] System.out.println( comp1 == comp2 ); // false // decorate2 メソッドを使って装飾 comp1 = new ConcreteStringComponent("aaa"); comp2 = decorate2(comp1); System.out.println( comp1.getString() ); // [ aaa ] System.out.println( comp2.getString() ); // [ aaa ] System.out.println( comp1 == comp2 ); // true } public static StringComponent decorate1(StringComponent comp) { ConcreteDecoratorA deco = new ConcreteDecoratorA(); StringComponentDecorator.decorate(comp, deco); return deco; } public static StringComponent decorate2(StringComponent comp) { StringComponentDecorator.decorate(comp, new ConcreteDecoratorA()); return comp; } }この例から見れるように、各 ConcreteDecorator クラスの役割をもったクラス (この例では ConcreteDecoratorA) は、Decorator パターンのそれと同じように、より上位の階層の型 (この例では ConcreteDecoratorA は StringComponent interface を実装している) で参照することができる。
結果 (欠点):
Component component = new ConcreteDecoratorA( new ConcreteDecoratorB( new ConcreteComponent() ) );しかし、ここで紹介しているテクニックを使う場合には同じようなことをしようとするとコードがやや長くなる:
Component comp = new ConcreteComponent(); ComponentDecorator.decorate(comp, new ConcreteDecoratorB() ); ComponentDecorator.decorate(comp, new ConcreteDecoratorA() );
サンプルコード (実装例その1。その 2 は下のほうを参照):
package dp.decorator; public class ConcreteStringComponent { private String str; public ConcreteStringComponent(String str) { this.str = str; } public String getString() { return str; } }
package dp.decorator; public interface StringComponent { }
package dp.decorator; public class ConcreteDecoratorA { public String getString() { StringComponent comp = getComponent(); return "[ " + comp.getString() + " ]"; } }
package dp.decorator; public class ConcreteDecoratorB { public String getString() { StringComponent comp = getComponent(); return "***" + comp.getString() + "***"; } }
package dp; import dp.decorator.*; public class Client { public static void main(String[] args) { StringComponent comp = new ConcreteStringComponent("aaa"); System.out.println( comp.getString() ); // aaa StringComponentDecorator.decorate(comp, new ConcreteDecoratorA() ); System.out.println( comp.getString() ); // [ aaa ] StringComponentDecorator.decorate(comp, new ConcreteDecoratorB() ); System.out.println( comp.getString() ); // *** [ aaa ] *** StringComponentDecorator.decorate(comp, new ConcreteDecoratorA() ); System.out.println( comp.getString() ); // [ *** [ aaa ] *** ] } }
package dp; import java.util.Map; import java.util.HashMap; public aspect StringComponentDecorator { private static Map decorationMap = new HashMap(); declare parents : ConcreteStringComponent implements StringComponent; declare parents : StringComponentConcreteDecorator implements StringComponent; declare parents : ConcreteDecoratorA implements StringComponentConcreteDecorator; declare parents : ConcreteDecoratorB implements StringComponentConcreteDecorator; public interface StringComponentConcreteDecorator { } private StringComponent StringComponentConcreteDecorator.component; public StringComponent StringComponentConcreteDecorator.getComponent() { return component; } private void StringComponentConcreteDecorator.setComponent(StringComponent component) { this.component = component; } public abstract String StringComponent.getString(); public static void decorate(StringComponent component, StringComponentConcreteDecorator decorator) { if ( getDecorator(component) == null) { decorator.setComponent( component ); } else { decorator.setComponent( getDecorator(component) ); } decorationMap.put(component, decorator); } private static StringComponentConcreteDecorator getDecorator(StringComponent component) { return (StringComponentConcreteDecorator)decorationMap.get(component); } pointcut componentObj(ConcreteStringComponent component) : this(component) && !cflow( this(StringComponentConcreteDecorator+) ) && if ( getDecorator(component) != null ); pointcut getString(ConcreteStringComponent component) : componentObj(component) && execution( String ConcreteStringComponent.getString() ); Object around(StringComponent component) : getString(component) { return getDecorator(component).getString(); } }
aaa [ aaa ] *** [ aaa ] *** [ *** [ aaa ] *** ]
実装:
たとえば、上述のサンプルコードにたいして、新しい getString2() というメソッドを追加することになったとしよう。このメソッドは関連のあるすべてのクラスが実装する必要があるため、関連のあるすべてのクラスが実装する interface である StringComponent interface で定義するとする。実際には、StringComponentDecorator アスペクト内で Introduction を使ってこの新しいメソッドを定義する。
public aspect StringComponentDecorator { ... public abstract String StringComponent.getString(); public abstract String StringComponent.getString2(); ... }これにより、StringComponent interface を実装するすべてのクラスが String getString2() メソッドを実装することが要求される。この例では、ConcreteStringComponent クラス、ConcreteDecoratorA クラス、ConcreteDecoratorB クラスがその対象となる。これらのクラスのそれぞれに対して、以下のような String getString2() メソッドをそれぞれ実装することにする。
public class ConcreteStringComponent { ... public String getString2() { return str + " " + str; } }
public class ConcreteDecoratorA { ... public String getString2() { return "[[ " + getComponent().getString2() + " ]]"; } }
public class ConcreteDecoratorB { ... public String getString2() { return "*** ***" + getComponent().getString2() + "*** ***"; } }StringComponentDecorator アスペクトにも、この getString2() メソッドが呼ばれたときに、正しく装飾された振る舞いをさせるためにも、コードの変更が必要になる。ここでは、リフレクションをうまく使うことで、各メソッド (getString() か、あるいは getString2() )を明示的に指定することを避けている。
import java.lang.reflect.Method; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.Signature; public aspect StringComponentDecorator { ... pointcut executeMethod(ConcreteStringComponent component) : componentObj(component) && execution( * ConcreteStringComponent.*(..) ); Object around(StringComponent component) : executeMethod(component) { return invokeMethod( getDecorator(component), thisJoinPoint ); } private static Object invokeMethod(Object target, JoinPoint joinpoint) { try { Object[] args = joinpoint.getArgs(); Class[] paramTypes = toClass( args ); String name = joinpoint.getSignature().getName(); Method method = target.getClass().getDeclaredMethod(name, paramTypes); return method.invoke(target, args); } catch (Exception e) { throw new RuntimeException(e); } } private static Class[] toClass(Object[] args) { Class[] types = new Class[args.length]; for(int i = 0 ; i < args.length ; i++) { types[i] = args[i].getClass(); } return types; } }実行結果は以下のようになる。
package dp; import dp.decorator.*; publi class Client { public static void main(String[] args) { StringComponent comp = new ConcreteStringComponent("aaa"); System.out.println( comp.getString2() ); // aaa aaa StringComponentDecorator.decorate(comp, new ConcreteDecoratorA() ); System.out.println( comp.getString2() ); // [[ aaa ]] StringComponentDecorator.decorate(comp, new ConcreteDecoratorB() ); System.out.println( comp.getString2() ); // *** *** [[ aaa aaa ]] *** *** StringComponentDecorator.decorate(comp, new ConcreteDecoratorA() ); System.out.println( comp.getString2() ); // [[ *** *** [[ aaa aaa ]] *** *** ]] } }ここで、装飾対象としているメソッドの戻り値の型が void であったとしても上記のコードにより正常に動作にすることに注意していただきたい。たとえば、void printString() というメソッドを getString2() メソッドを新たに定義したときと同様にして StringComponent interface に新しく定義するとしよう。
public aspect StringComponentDecorator { ... public abstract String StringComponent.getString(); public abstract String StringComponent.getString2(); public abstract void StringComponent.printString(); ... }StringComponent interface に新しいメソッドである void printString() を定義することにより、次の 3 つのクラスがそのメソッドを実装するように直接影響をうける: ConcreteStringComponent クラス、ConcreteDecoratorA クラス、ConcreteDecoratorB クラス。それぞれに対して、以下のような単純な実装を行った。
public class ConcreteStringComponent { ... public void printString() { System.out.println(str); } }
public class ConcreteDecoratorA { ... public void printString() { System.out.print("[[[ "); getComponent().printString(); } }
public class ConcreteDecoratorB { ... public void printString() { System.out.print("*** "); getComponent().printString(); } }実行結果は以下のようになる。
package dp; import dp.decorator.*; publi class Client { public static void main(String[] args) { StringComponent comp = new ConcreteStringComponent("aaa"); comp.printString(); // aaa StringComponentDecorator.decorate(comp, new ConcreteDecoratorA() ); comp.printString(); // [[[ aaa StringComponentDecorator.decorate(comp, new ConcreteDecoratorB() ); comp.printString(); // *** [[[ aaa StringComponentDecorator.decorate(comp, new ConcreteDecoratorA() ); comp.printString(); // [[[ *** [[[ aaa } }
public aspect StringComponentDecorator { // ... その他のコード public static void undecorate(StringComponent component) { if ( getDecorator(component) != null) { StringComponentConcreteDecorator deco = getDecorator(component); if ( deco.getComponent() == component ) { decorationMap.remove(component); } else { decorationMap.remove(component); decorationMap.put(component, deco.getComponent() ); } } } }たとえば、以下のようにして使用する:
package dp; import dp.decorator.*; public class Client { public static void main(String[] args) { StringComponent comp = new ConcreteStringComponent("aaa"); System.out.println( comp.getString() ); // aaa StringComponentDecorator.decorate(comp, new ConcreteDecoratorA() ); System.out.println( comp.getString() ); // [ aaa ] StringComponentDecorator.undecorate(comp); System.out.println( comp.getString() ); // aaa } }実行結果は、以下のとおり
aaa [ aaa ] aaa同様にして、すべての decorator を一度に取り外すようなオペレーションの実装も可能である:
public aspect StringComponentDecorator { // ... その他のコード public static void undecorateAll(StringComponent component) { while( getDecorator(component) != null ) { undecorate(component); } } }以下のようにして使用する:
package dp; import dp.decorator.*; public class Client { public static void main(String[] args) { StringComponent comp = new ConcreteStringComponent("aaa"); System.out.println( comp.getString() ); // aaa StringComponentDecorator.decorate(comp, new ConcreteDecoratorA() ); System.out.println( comp.getString() ); // [ aaa ] StringComponentDecorator.decorate(comp, new ConcreteDecoratorB() ); System.out.println( comp.getString() ); // *** [ aaa ] *** StringComponentDecorator.undecorateAll(comp); System.out.println( comp.getString() ); // aaa } }実行結果は以下のとおり:
aaa [ aaa ] *** [ aaa ] *** aaa
package dp; import dp.decorator.*; public class Client { public static void main(String[] args) { StringComponent comp = new ConcreteStringComponent("aaa"); System.out.println( comp.getString() ); // aaa Decorator.decorate(comp, new ConcreteDecoratorA() ); System.out.println( comp.getString() ); // [ aaa ] Decorator.decorate(comp, new ConcreteDecoratorB() ); System.out.println( comp.getString() ); // *** [ aaa ] *** } }
package dp.decorator; public class Decorator { }
package dp.decorator; import java.util.Map; import java.util.HashMap; public abstract aspect DecoratorProtocol { private Map decorationMap = new HashMap(); private Map pairMap = new HashMap(); abstract pointcut callGetComponent(); pointcut getComponent(Object decorator) : target(decorator) && callGetComponent(); Object around(Object decorator) : getComponent(decorator) { return pairMap.get(decorator); } protected void decorate(Object component, Object decorator) { if ( getDecorator(component) == null) { pairMap.put(decorator, component); } else { pairMap.put(decorator, getDecorator(component)); } decorationMap.put(component, decorator); } protected Object getDecorator(Object component) { return decorationMap.get(component); } }
package dp.decorator; public aspect StringComponentAspect extends DecoratorProtocol { declare parents : ConcreteStringComponent implements StringComponent; declare parents : StringComponentDecorator implements StringComponent; declare parents : ConcreteDecoratorA implements StringComponentDecorator; declare parents : ConcreteDecoratorB implements StringComponentDecorator; protected interface StringComponentDecorator { } public static void Decorator.decorate(StringComponent component, StringComponentDecorator decorator) { StringComponentAspect.aspectOf().decorate(component, decorator); } public StringComponent StringComponentDecorator.getComponent() { throw new RuntimeException("implementation error"); } public abstract String StringComponent.getString(); pointcut callGetComponent() : call(StringComponent StringComponentDecorator.getComponent() ); pointcut componentObj(ConcreteStringComponent component) : this(component) && !cflow( this(StringComponentDecorator+) ) && if ( StringComponentAspect.aspectOf().getDecorator(component) != null ); pointcut getString(ConcreteStringComponent component) : componentObj(component) && execution( String ConcreteStringComponent.getString() ); Object around(StringComponent component) : getString(component) { return getDecorator(component).getString(); } private StringComponentDecorator getDecorator(StringComponent component) { return (StringComponentDecorator)super.getDecorator(component); } }実装:
package dp.decorator; public abstract aspect DecoratorProtocol { // ... その他のコード protected void undecorate(Object component) { Object decorator = getDecorator(component); if ( getComponent( decorator ) == component ) { decorationMap.remove(component); } else { decorationMap.put(component, getComponent(decorator) ); } } }
package dp.decorator; public aspect StringComponentAspect extends DecoratorProtocol { // ... その他のコード // 以下の Introduction は他のアスペクトでの定義も可能 public static void Decorator.undecorate(StringComponent component) { StringComponentAspect.aspectOf().undecorate(component); } // ... その他のコード }以下のようにして使用する:
package dp; import dp.decorator.*; public class Client { public static void main(String[] args) { StringComponent comp = new ConcreteStringComponent("aaa"); System.out.println( comp.getString() ); // aaa Decorator.decorate(comp, new ConcreteDecoratorA() ); System.out.println( comp.getString() ); // [ aaa ] Decorator.undecorate(comp); System.out.println( comp.getString() ); // aaa } }実行結果は以下のとおり:
aaa [ aaa ] aaa同様にして、すべての decorator を一度に取り外すようなオペーレーションの実装も簡単にできる:
package dp.decorator; public abstract aspect DecoratorProtocol { // ... その他のコード protected void undecorate(Object component) { /* ... */ } protected void undecorateAll(Object component) { while( getDecorator(component) != null) { undecorate(component); } } }
package dp.decorator; public aspect StringComponentAspect extends DecoratorProtocol { // ... その他のコード // 以下の Introduction は他のアスペクトでの定義も可能 public static void Decorator.undecorate(StringComponent component) { /* ... */ } public static void Decorator.undecorateAll(StringComponent component) { StringComponentAspect.aspectOf().undecorateAll(component); } // ... その他のコード }以下のようにして使用する:
package dp; import dp.decorator.*; public class Client { public static void main(String[] args) { StringComponent comp = new ConcreteStringComponent("aaa"); System.out.println( comp.getString() ); // aaa Decorator.decorate(comp, new ConcreteDecoratorA() ); System.out.println( comp.getString() ); // [ aaa ] Decorator.decorate(comp, new ConcreteDecoratorB() ); System.out.println( comp.getString() ); // *** [ aaa ] *** Decorator.undecorateAll(comp); System.out.println( comp.getString() ); // aaa } }実行結果は以下の通り:
aaa [ aaa ] *** [ aaa ] *** aaa
public aspect DecoratorAspect { public static void Decorator.decorate(StringComponent component, StringComponentAspect.StringComponentDecorator decorator) { StringComponentAspect.aspectOf().decorate(component, decorator); } public static void Decorator.undecorate(StringComponent component) { StringComponentAspect.aspectOf().undecorate(component); } public static void Decorator.undecorateAll(StringComponent component) { StringComponentAspect.aspectOf().undecorateAll(component); } }こうすると、特定のアスペクト (例では StringComponentAspect) に対するコードの修正は必要なく、コードは必要以上に汚れることを避けることができる。
この DecoratorAspect は、アプリケーション内に存在する DECORATOR パターンを採用するすべてを定義する役目を持つと考えられる。つまり、この例でいえば StringComponentAspect アスペクト以外のその他のアスペクト (たとえば IntegerComponentAspect など) に関するコードも DecoratorAspect に定義する。
更新履歴