A Day In The Life

とあるプログラマの備忘録

総称性(Generics)を使って拡張性の高いクラスを作成する。

オブジェクト指向技術を活用していくための考察第3回目です。

今回は型の総称性(Generics)をうまく使って拡張性の高いクラスを作成していきます。
型の総称性はJavaでも5.0から導入されたので最近よく耳にすると思います。
ただ多くの方はコレクションを扱うときにキャストが不要になったぐらいの認識しかされていないのではないでしょうか。
しかし総称性(Generics)はオブジェクト指向(厳密に言うと静的型付けオブジェクト指向言語)を理解する上でとても大切な概念なのです。

例えば、オブジェクト指向入門で有名なバートランド・メイヤーは著書の中で「拡張性、再利用性、信頼性のためにクラスをより柔軟なものにしなければならない。これには2つの方向性がある。ひとつは抽象化、具体化の軸で、もうひとつは型のパラメータ化、総称性である。」と言っています。

継承と総称性を図にすると以下のようになります。

継承と総称性の関係

図1:継承と総称性の関係
今回は前回登場したユーザー定義型UnitPrice,Quantity,SubTotalを使って実際にJavaで実装しながら説明してきます。

まずは前回のコードをみてみます。

public class UnitPrice {
  private BigDecimal value;
  public UnitPrice(String val) {
    this.value = new BigDecimal(val);
  }
  //数量をかける
  public SubTotal multiply(Quantity qty) {
    return new SubTotal(
        this.value.multiply(new BigDecimal(qty.getValue())).toString());
  }
 @Override
  public String toString() {
    return value.toString();
  }
  @Override
  public boolean equals(Object other) {
    return value.equals(other);
  }
  @Override
  public int hashCode() {
    return this.value == null ?
        System.identityHashCode(this) :
        this.value.hashCode();
  }
}

public class Quantity {
  private Integer value;
  public Quantity(String val) {
    this.value = new Integer(val);
  }
  public Integer getValue() {
    return value;
  }
  @Override
  public String toString() {
    return value.toString();
  }
  ...equalsとhashCodeメソッドはUnitPriceクラスと同じ
}

public class SubTotal {
  private BigDecimal value;
  public SubTotal(String val) {
    this.value = new BigDecimal(val);
  }
  public BigDecimal getValue() {
    return value;
  }
  @Override
  public String toString() {
    return value.toString();
  }
  ...equalsとhashCodeメソッドはUnitPriceクラスと同じ
}

よく見るとこの3つのクラスに以下のような共通した部分があるのがわかります。

UnitPriceとSubTotalはインスタンス変数の型も同じなので共通化できそうですね。

とりあえずQuantityはおいといてUnitPriceとSubTotalに共通する部分を抽出して親クラスを作ってみましょう。

親クラスは数字を扱うクラスということでNumericいう名前にします。

public class Numeric {
  private BigDecimal value;
  public Numeric(String val) {
    this.value = new BigDecimal(val);
  }
  public BigDecimal getValue() {
    return value;
  }
  @Override
  public String toString() {
    return value.toString();
  }
  @Override
  public boolean equals(Object other) {
    return value.equals(other);
  }
  @Override
  public int hashCode() {
    return this.value == null ?
        System.identityHashCode(this) :
        this.value.hashCode();
  }
}

ではこの親クラスを継承してUnitPriceとSubTotalも修正しましょう

public class UnitPrice extends Numeric {
  public UnitPrice(String val) {
    super(value);
  }
  //数量をかける
  public SubTotal multiply(Quantity qty) {
    return new SubTotal(
        getValue().multiply(new BigDecimal(qty.getValue())).toString());
  }
}

public class SubTotal extends Numeric {
  public SubTotal (String value) {
    super(value);
  }
}

Quantityも同じように親クラスを継承したいのですがインスタンス変数がIntegerのためできそうにありません。
そこで総称性を使った親クラスを作成しQuantityからも継承できるようにしてみましょう。

public class Numeric<G extends Number> {
 private G value;
 /* コンパイルエラーになるためコメントアウト
 public Numeric(String value) {
  this.value = new G(value);
 }
 */
 public Numeric(G value) {
  this.value = value;
 }
 public G getValue() {
  return value;
 }
 @Override
 public String toString() {
  return value.toString();
 }
  @Override
  public boolean equals(Object other) {
    return value.equals(other);
  }
  @Override
  public int hashCode() {
    return this.value == null ?
        System.identityHashCode(this) :
        this.value.hashCode();
  }
}

ここでひとつ困ったことが・・・JavaではパラメータクラスGをnewできないのです。(C#2.0のGenericsではpublic class Numeric where G : Number, new()と宣言することでパラメータクラスをnewできるようです。詳しくはC# 2.0入門を参照してください。)
しかたがないので子クラスのコンストラクタでパラメータをnewして渡してやることにします。

public class UnitPrice extends Numeric<BigDecimal> {
 public UnitPrice(String val) {
  super(new BigDecimal(value));
 }
 //数量をかける
 public SubTotal multiply(Quantity qty) {
  return new SubTotal(
     getValue().multiply(new BigDecimal(qty.getValue())).toString());
 }
}

public class SubTotal extends Numeric<BigDecimal> {
 public SubTotal (String value) {
  super(new BigDecimal(value));
 }
}

public class Quantity extends Numeric<Integer> {
 public Quantity (String value) {
  super(new Integer(value));
 }
}

総称性を使うことでQuantityも親クラスを継承できるようになりました。
Numericクラスに総称性を使ったことで適応範囲が広がったのがわかるのではないでしょうか。
図にすると以下のようになります。総称性によって拡張性が横に広がっていくのがわかるかと思います。
今回作成したクラスの位置関係
図2:今回作成したクラスの位置関係
今回はとても簡単な例を示しましたが、総称性がいろんな場面で応用できる非常に強力な概念であることが理解していただけたかと思います。