読者です 読者をやめる 読者になる 読者になる
無料で使えるシステムトレードフレームワーク「Jiji」 をリリースしました!

・OANDA Trade APIを利用した、オープンソースのシステムトレードフレームワークです。
・自分だけの取引アルゴリズムで、誰でも、いますぐ、かんたんに、自動取引を開始できます。

ServiceのAPIをどげんかせんといかん

Java

次のような、オブジェクト入出力クラスがあって、

// フォルダ操作サービス
interface FolderService {
    // ID(オブジェクトを示すパスを持つクラス)を指定してフォルダを得る。
    Folder get( ID id );
    // フォルダを登録する。
    void put( Folder f );
}
// ファイル操作サービス
interface FileService {
    File get( ID id );
    void put( File f );
}
...

現状では、これが操作対象のオブジェクトごと(FileとかFolderとかほかたくさん)にそれぞれ用意されているんですが、これだと以下の問題があるわけですよ。

  • (利用者側) 操作対象に対応するServiceを用意して、呼び分けないといけない。
  • (実装側) 操作対象ごとにServiceの実装をするのがメンドイ。

なので、APIを変更して、1つのServiceでFileもFolderも操作できるようにしたい。

// Entity(File,Folderを汎化したもの)を操作するサービス。
interface EntityService {
    // File または フォルダを取得する。
    Entity get( ID id );
    // File または フォルダを登録する。
    void put( Entity f );
}

putは引数でEntityを受け取ればOKだけど、問題はget。なるべく安全(クラスキャストのへま率を低く)で簡単に使える(明示的なキャストとかしなくてもOK)APIにしたい。ということで3つ案を考えてみた。

1.Serviceの型パラメータで指定

Serviceの型パラメータで受け取る型を指定できるようにするやり方。

  • 型をFileやFolderに指定すれば、getでFileやFolderを直接取得できる。
  • 型をEntityにすればFile,Folderを両方putできる。
// 型パラメータで受け取る型を指定できるようにする。
interface EntityService01<T extends Entity> {
    T get( ID id );
    void put( T f );
}

使い方はこんな感じ。

// パラメータで明示すれば、getで型キャストなしにオブジェクトを取り出せる。
EntityService01<File> fileService = createEntityService01();
File file = fileService.get( id );
fileService.put( file );

EntityService01<Folder> folderService = createEntityService01();
Folder folder = folderService.get( id );
folderService.put( folder );

// パラメータをEntityにすれば、
// File,Folderの両方をputできるサービスとなる。
// ただし、getの戻り値はEntityになる。
EntityService01<Entity> entityService = createEntityService01();
Entity entity = entityService.get( id );
// file   = entityService.get( id ); // 派生クラスで取得することはできない
// folder = entityService.get( id );
entityService.put( entity ); //
entityService.put( file );
entityService.put( folder );
  • ○Serviceの実装は1つでOK。
  • ×利用者側の問題は解決していない。複数のServiceを用意して呼び分けないといけない。

2.代入先変数の型から推論

代入先変数の型から推論させる方式。利用者側はIDに対応するオブジェクトが何かわかっている前提なので、代入先の型に応じてキャストして返しますよ、という仕組み。

interface EntityService02 {
    <T extends Entity> T get( ID id );
    <T extends Entity> void put( T f );
}

利用者側は次の通り。

EntityService02 service = createEntityService02();
File file = service.get( id ); // 代入先変数の型からの推論で戻り値の型が決まる。
Folder folder = service.get( id );
service.put( file );
service.put( folder );

// 戻り値はEntityに限定されているので、以下はコンパイルエラー。
//String s = service.get( id );
  • ○Serviceの実装は1つでOK。
  • ○利用者側も呼び分け不要。
  • ×推論が上手くできない場合がある。例えば以下のような場合。
// Fileを受け付ける関数foo
public static void foo( File f ) {}
...
EntityService02 service = createEntityService02();
foo( service.get( id ) ); // コンパイルエラーになってしまう!

3.IDの型パラメータで特定

オブジェクトの種類はIDごとに決まるので、IDに型パラメータを追加して、引数で渡すIDのそれで戻り値のオブジェクトが決まるようにするとか。

// 型パラメータ付きID
class ID<T extends Entity> {}
// サービス
interface EntityService03 {
    <T extends Entity> T get( ID<T> id );
    void put( Entity f );
}

サンプルコード。

// IDに型パラメータを付ける。
ID<File> fileId = null;
ID<Folder> folderId = null;

EntityService03 service = createEntityService03();
File file = service.get( fileId ); // IDの型パラメータで戻り値が決まる。
// Folder folder = service.get( fileId ); // これはコンパイルエラー
Folder folder = service.get( folderId );
service.put( file );
service.put( folder );

foo( service.get( fileId ) );
//foo( service.get( folderId ) ); // コンパイルエラー

// 型が不明な場合はEntityでのみ取り出せる。
ID xId = null;
Entity entity = service.get( xId );
//File f = service.get( xId ); // コンパイルエラー
  • ○Serviceの実装は1つでOK。
  • ○利用者側も呼び分けも不要。

どれがいいか?

ということで個人的には「3」がいいんじゃないかなーと思うんですがどうでしょうか? > だれ?