ServiceのAPIをどげんかせんといかん
次のような、オブジェクト入出力クラスがあって、
// フォルダ操作サービス 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」がいいんじゃないかなーと思うんですがどうでしょうか? > だれ?