はじめに
本稿では、ジェネリックを利用してコンパイル時の動的処理およびクライアント関連の型安全性(type-safety)を実現する方法について解説します。一般的に、サブクラス化を行う際の最も重要な側面は、クラス固有の機能を実現するために、いかにして同じメソッドパラメータを使ってオーバーライドを実現するかということです。場合によっては、クラス固有のパラメータが必要になることもあるかもしれません。さらに、オーバーライドメソッドが、こうしたクラス固有パラメータのスーパークラスであるパラメータを使用する場合も考えられます。このようなメソッドの例としては、パブリックAPIを通じて公開され、具象実装クラス内でオーバーライドされるメソッドが挙げられます。
シナリオ
本稿で取り上げるのは非常によくあるシナリオで、多くの人が過去に直面したことがあり、同じような方法で解決してきたのではないかと思います。ここでは、単純なレンタカーシステムの例を使ってジェネリックの利用方法を説明します。
今回の例では、メインのレンタルサービスクラスをRentVehicleManagerとしますが、このクラスはインターフェイスにしても抽象クラスにしてもかまいません。RentCarManagerとRentBikeManagerはRentVehicleManagerのサブクラスで、RentVehicleManagerのメソッドをそれぞれの機能に合わせてオーバーライドします。このシナリオの実用的な実装ソリューションとしては、次のようなものが考えられます。
- 一連のManagerクラスの作成にFactoryパターンを使用する
- パラメータの共通階層を作り、メソッド内で厳密なチェックを行う
- ジェネリックを利用した共通階層を使用する
方法1は、関連クラスのインスタンス生成という点では優れたソリューションですが、オブジェクトパラメータの一般化は実現できません。
方法2のソリューションは、たとえば図2のようになります。この場合、個々のオーバーライドメソッドは、そのクラスにふさわしい型の値が引数に渡されているかどうかをメソッドの冒頭で厳密にチェックしなければなりません。
この場合のインターフェイスは、たとえば次のようになります。
package com.sumithp.codeguru.nongeneric.vehicle; import com.sumithp.codeguru.vehicle.domain.Vehicle; public interface RentVehicleMgr { public void rentOut(Vehicle vehicle); public void checkIn(Vehicle vehicle); public void diagnose(Vehicle vehicle); public void repair(Vehicle vehicle); }
方法2の場合、バイクレンタルに関する実装はたとえば次のようになります。
package com.sumithp.codeguru.nongeneric.vehicle; import com.sumithp.codeguru.vehicle.domain.Vehicle; public class RentBikeMgrImpl implements RentVehicleMgr { // If we don't use Vehicle as the parameter here, the clients // will not be able to use a generalized interface to call our // methods. public void rentOut(Vehicle vehicle) { // if (vehicle instanceof bike) // Renting Out Related DB Operations } public void checkIn(Vehicle vehicle) { // if (vehicle instanceof bike) // Vehicle Check In Related DB Operations } public void diagnose(Vehicle vehicle) { // if (vehicle instanceof bike) // Self Diagnose functionality of a vehicle // Print diagnosis } public void repair(Vehicle vehicle) { // if (vehicle instanceof bike) // Perform pre-defined repair // Print repair details } }
方法2の場合、コンパイル時のクライアント側の使い方は次のようになるでしょう。
package com.sumithp.codeguru.nongeneric.vehicle.client; import com.sumithp.codeguru.nongeneric.vehicle.RentBikeMgrImpl; import com.sumithp.codeguru.nongeneric.vehicle.RentCarMgrImpl; import com.sumithp.codeguru.nongeneric.vehicle.RentVehicleMgr; import com.sumithp.codeguru.vehicle.domain.Bike; import com.sumithp.codeguru.vehicle.domain.Car; import com.sumithp.codeguru.vehicle.domain.Vehicle; public class RentNonGenericVehicleClient { public void rentBike() { // You want only one interface to handle all rentals RentVehicleMgr rentVehicleMgr; rentVehicleMgr = new RentBikeMgrImpl(); Vehicle vehicle = new Bike(104,"TWO",true,150); rentVehicleMgr.rentOut(vehicle); /* * Client can as well do this * * Vehicle vehicle = new Car(104,"FOUR",true,"PETROL"); * rentVehicleMgr.rentOut(vehicle); * * If there are no instanceof checks, this bombs! * */ } public void rentCar() { // You want only one interface to handle all rentals RentVehicleMgr rentVehicleMgr; rentVehicleMgr = new RentCarMgrImpl(); Vehicle vehicle = new Car(104,"FOUR",true,"PETROL"); rentVehicleMgr.rentOut(vehicle); /* * Client can do the same as shown for rentBike() * * Vehicle vehicle = new Bike(104,"TWO",true,150); * rentVehicleMgr.rentOut(vehicle); * * If there are no instanceof checks, this bombs too! * */ } }
方法3は、最も完成度が高く、有効なソリューションです。必要なメソッド群は1つのクラスで公開され、各メソッドはジェネリック変数を通じてそれぞれの実装を提供するため、一貫性に優れています。それでは、このソリューションについて詳しく見ていきましょう。