yojikのlog

yojikのブログです

RoleObjectっぽいパターン(<del datetime="2006-03-31T13:38:29+09:00">名前未定</del>)

ちょっとごにょごにょ思いついたのでメモっておきます。長くなりそう。
既存のオブジェクト指向システムの問題のひとつが「関心のもつれ合い」。例えばドメインオブジェクトを複数の関心(ユースケース)に対応させるためには、お互い関連性が無かったりする複数のロジックをドメインに乗っける必要があり、実装が複雑になってしまう。UC側で継承してロジックを追加してもいいけど、ORマッピングしてる場合などは面倒になりそう。
「そこでアスペクト指向ですよ!」というのが「ユースケースによるアスペクト指向ソフトウェア開発」の主旨。この場合はインタータイプなどの機能をつかって、関心を織り込むといった形で実装するはず。
アスペクトが使えない場合は、本書ではAdapterパターン*1が使えるというヒントがあるのだけど、RoleObjectパターンも使えそうな気がする。
RoleObjectは簡単にいえばDecoratorパターン等の変種みたいなもので、クラスにロールを持たせて、ロール側にメソッドやら属性を追加するというパターン。ただ過度に複雑でキャストとかRoleの登録が非常にウザいのでイマイチ。
で、Java5ならGenericsがあるので改良できそうな気がしてきた。
イメージはこんな感じ。本来はRoleの取得はgetRole()なんだけど、as()というメソッド名にしてみた。

Customer customer = new Customer();
customer.as(GoldMember.class).goldService();
customer.as(PlatinumMember.class).platinumService();

「CustomerをGoldMemberとして扱う」みたいな感じで自然につかえて、キャストいらず。
DT脳*2の人にも受け入れやすい感じ。UC固有のロジックをRoleという形で実装して既存のドメインオブジェクトにアタッチできる。

以下適当なパターン形式で。パターン名募集中!! コメント欄でいい感じの名称があったので決定。「寸劇依頼」

問題と制約

共通に使われるクラス(例えばEntityを表すクラス)に対して特定用途のロジックを追加する際に、以下の制約があるとする

  • 直接ロジックを追加したくない
  • ORマッピングモデルを崩す等の理由で継承も使いづらい
    • またORマッパーから取得するEntityにはAdapterやらDecoratorを被せずらい。(DIもやりづらい)
  • (追記)Decoratorを使う場合は、Decoratorオブジェクトの管理をクライアントで行なわなくてはならない。
  • RoleObjectパターンはRoleの登録がめんどくさい。

ソリューション

  • 特定用途のロジックをRoleという形で実装して既存のオブジェクトにアタッチする
  • Roleオブジェクトはターゲットのクラスへの参照をもち、追加ロジックを実装する。
  • 対象クラスの as(RoleObject.class)というメソッドを追加し、Roleの作成、登録(キャッシング)、取得を同時に行う。
  • Genericsを使ってキャストを不要にする
  • 本来のRoleObjectパターンではRoleがターゲットのインタフェースを実装したり、登録削除のインタフェース持ってたりするけど省略。
    • そもそもクライアントはRoleの具象クラスを知っているわけだし、ここらへんは元パターンが複雑すぎる。
まずRoleのインタフェース。汎用的
import java.util.*;
interface Role<T> {
    public void original(T t);
}
Roleをキャッシュするための実装。汎用的
class RoleCash<T> { 
    Map<Class, Role<T>> roleCashMap = new HashMap<Class,Role<T>>();
    <R extends Role<T>> R get(T t, Class<R> clazz) throws Exception {
         Role<T> role = roleCashMap.get(clazz);
         if(role == null) {
             role = clazz.newInstance();
             role.original(t);
             roleCashMap.put(clazz, role);
         }
         return clazz.cast(role);
    }
}
Roleを持つクラスの例。asメソッドによって、RoleCashからRoleObjectを取得する
class Customer {
    String name="yojik";
    RoleCash<Customer> roleCash = new RoleCash<Customer>(); 
    <T extends  Role<Customer>> T as(Class<T> roleClazz) throws Exception  {
        return roleCash.get(this,roleClazz);
    }
}
Role具象クラス。 「Roleを持つオブジェクト」への参照を設定するメソッドが必須
class GoldMember implements Role<Customer> {
    Customer original;
    public void original(Customer customer) {
        this.original=customer;
    }
    public void goldService() {
        System.out.printf("GoldService for %s as %s \n", original.name,  this);
    }
}
class PlatinumMember  implements Role<Customer> {
    Customer original;
    public void original(Customer customer) {
        this.original=customer;
    }
    public void platinumService() {
        System.out.printf("PlatinumService for %s as %s \n", original.name, this);
    }
}
使用例
public class SampleMain {
    public static void main(String[] args) throws Exception{
        Customer customer = new Customer();
        customer.as(GoldMember.class).goldService();
        customer.as(PlatinumMember.class).platinumService(); 
    }
}

以上Generics部分に自信が無いのだけど、こんな感じです。もしかしたら既出かも。

*1:多分ドメインに被せて使う

*2:ダイナミックタイピングな言語しか愛せない脳の人。たかはしさん命名