読者です 読者をやめる 読者になる 読者になる

さんぽみち

なにか思いついたときとかに気まぐれに更新されます。

Java Decoratorの装飾関係をリストで表現する

Java デザインパターン

はじめに

 Decorator パターンにおいて、 Component と Decorator は Component を親の階層に持つ 1 : 1 の関係にあります。
 子ノードが常に一つの木構造を想像すると近いと思います。
 元から使いやすいパターンではありますが、この "階層を持つ" というのが曲者で扱いづらく感じる
場合があります。
 そこで、子ノードが一つの木はリスト構造をほぼ同等である為、この階層をリストにすることが
できるのではないかと思いつきました。

リスト構造がいい理由

 リスト構造にすると何が嬉しいかは、
1. Decoratorを適用する際、関係が縦でなく横となる為、インデントがそろい読みやすい
2. Decorator コンストラクタへの引数が読みやすい
3. 装飾階層に対して、Decorator の挿入・削除が容易に行える
4. Stream によって適用する Decorator を選択できる
などがあります。
 Decorator を適用する際、

new Decorator3(
  new Decorator2( true
    new Decorator1( 10,
      new Component()
    )
  )
);

のように、コンストラクタの中でインスタンス化を行う為、階層が深くなります。
 これを、

DecorateList deco = new DecorateList(){
  {
    new Component(),
    new Decorator1(),
    new Decorator2(true),
    new Decorator3(10)
  }
};

のような形で記憶しておき、利用の直前に関連付けがなされたComponentのインスタンスを得ることが目標です。

ソース

DecorateList.java

package decorator;

import java.util.Arrays;
import java.util.ArrayList;
import java.util.List;
import java.util.function.*;
import java.util.stream.*;

public class DecorateList<C> extends ArrayList<Function<C, ? extends C>>
{
    private static class GeneralContainer<T>
    {
        public static <T> GeneralContainer<T> create(T value)
        { return new GeneralContainer<T>(value); }
        
        public T value;
        public GeneralContainer(T value)
        { this.value = value; }
    }
    
    public static <C> Collector<Function<C, ? extends C>, GeneralContainer<C>, C> toComponent(C init)
    {
        return Collector.<Function<C, ? extends C>, GeneralContainer<C>, C>of(
            () -> GeneralContainer.create(init),
            (c, f) -> c.value = f.apply(c.value),
            (c1, c2) -> null,
            c -> c.value
        );
    }
    
    @SafeVarargs
    public static <C> DecorateList<C> create(Function<C, ? extends C>... args)
    { return new DecorateList<C>(args); }
    @SafeVarargs
    public static <C> DecorateList<C> createFromDecorable(Decorable<C>... args)
    { return new DecorateList<C>(Arrays.stream(args).map( d -> d::attach )); }
    
    public DecorateList()
    { }
    
    public DecorateList(List<Function<C, ? extends C>> attachList)
    { super(attachList); }
    @SafeVarargs
    public DecorateList(Function<C, ? extends C>... args)
    { this(Arrays.asList(args)); }
    public DecorateList(Stream<Function<C, ? extends C>> stream)
    { this(stream.collect(Collectors.toList())); }
    
    public C getDecorated(C init)
    {
        C current = init;
        for(Function<C, ? extends C> item : this)
        {
            current = item.apply(current);
        }
        return current;
    }
}

Decorable.java

package decorator;

public interface Decorable<C>
{
    C attach(C parent);
}

 こんな感じになりました。
 ArrayList を継承させることで装飾の関係がいじりやすそうですね。
 使い方は、

// case normal decorator
DecorateList<Component> decoList = DecorateList.create(
    com -> new Decorator1(com),
    com -> new Decorator2(com),
    com -> new Decorator3(com)
);
Component component = decoList
    .stream()
    .limit(2)
    .collect(DecorateList.toComponent(new ConcreteComponent()));
// case decorable
DecorateList<DecorableComponent> decorableList = DecorateList.createFromDecorable(
    new DecorableDecorator1(),
    new DecorableDecorator2(),
    new DecorableDecorator3()
);
DecorableComponent component = decorableList
    .stream()
    .limit(2)
    .collect(DecorateList.toComponent(new ConcreteDecorableComponent()));

 のようになります。
 装飾の階層関係が順序関係に置き換わり読みやすい上に、stream の恩恵もばっちり受けることができます。
 Decorator の "組み立て方" を教えることによって、既存のソースに手を加えることなく利用することもできます。
 だいぶすっきりするので、使ってみてください。