Javaのemumのエレガントな使い方
- 評価
- クリップ 11
- VIEW 9,791
Javaのenumのエレガントな使い方を探しています。
これまで、①「定数を型安全かつ便利に使えるクラス」ぐらいの認識しかなく、そこまで価値を感じていませんでした。しかし、②「ストラテジーパターンを利用したいわゆる定数固有メソッド」(effective javaに載っているようなやつ)の存在を知ってから、次第にenumに興味を持つようになりました。
そこで質問です。実際の現場で見たことのあるenumのエレガントな使い方を教えてください。デザインパターンが盛り込まれてクラス数が多い重厚なやつでもいいですし、手軽に使えるシンプルな小技でもいいです。
インターネットで色々調べましたが、①もしくは②の説明が多いと感じましたので、これら以外の事例を希望します。
-
気になる質問をクリップする
クリップした質問は、後からいつでもマイページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
クリップを取り消します
-
良い質問の評価を上げる
以下のような質問は評価を上げましょう
- 質問内容が明確
- 自分も答えを知りたい
- 質問者以外のユーザにも役立つ
評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。
質問の評価を上げたことを取り消します
-
評価を下げられる数の上限に達しました
評価を下げることができません
- 1日5回まで評価を下げられます
- 1日に1ユーザに対して2回まで評価を下げられます
質問の評価を下げる
teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。
- プログラミングに関係のない質問
- やってほしいことだけを記載した丸投げの質問
- 問題・課題が含まれていない質問
- 意図的に内容が抹消された質問
- 過去に投稿した質問と同じ内容の質問
- 広告と受け取られるような投稿
評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。
質問の評価を下げたことを取り消します
この機能は開放されていません
評価を下げる条件を満たしてません
質問の評価を下げる機能の利用条件
この機能を利用するためには、以下の事項を行う必要があります。
- 質問回答など一定の行動
-
メールアドレスの認証
メールアドレスの認証
-
質問評価に関するヘルプページの閲覧
質問評価に関するヘルプページの閲覧
checkベストアンサー
+5
Java の Enum は手軽にインターフェースを実装し、ポリモフィズムを作れるところが便利です。
/**
* 会員
*/
class Member {
private int id;
private String name;
private MemberRank memberRank;
// アクセサ略
}
/**
* 会員ランクを表す列挙
*/
enum MemberRank {
/** 一般会員 */
GENERAL {
@Override
public BigDecimal getPrice(BigDecimal catalogPrice) {
// 一般会員は定価のまま
return catalogPrice;
}
},
/** シルバー会員 */
SILVER {
@Override
public BigDecimal getPrice(BigDecimal catalogPrice) {
// 5%引き
BigDecimal discountPrice = catalogPrice.multiply(new BigDecimal("0.05"));
return catalogPrice.subtract(discountPrice);
}
},
/** ゴールド会員 */
GOLD {
@Override
public BigDecimal getPrice(BigDecimal catalogPrice) {
// 10%引き
BigDecimal discountPrice = catalogPrice.multiply(new BigDecimal("0.1"));
return catalogPrice.subtract(discountPrice);
}
};
/**
* @param catalogPrice 定価
* @return ランクに応じた金額
*/
public abstract BigDecimal getPrice(BigDecimal catalogPrice);
}
このようなものを用意しておくと、ランク毎に if を切ることなく、
拡張性が高いコードになります。
// getMember はセッションなどからログイン中の会員情報を取得するものとする
BigDecimal price = getMember().getMemberRank().getPrice(catalogPrice);
追記1
MemberクラスのmemberRankプロパティに、各会員の状態を持たせる場合、どのような実装があり得ますか?
問題はこれです。
通常よくあるのは、データベースの会員情報から MemberRank を作成したいわけですから、
O/Rマッパーが Member取得時に自動で入れてくれたりもします。
データベースの値を MemberRank Enum に変換してくれます。
そういうのを省いて考えた場合どうなるかというと
例えば、以下のような感じだと台無しなわけです。
if (rank.equals("general")) {
member.setMemberRank(MemberRank.GENERAL);
} else if (rank.equals("silver")) {
member.setMemberRank(MemberRank.SILVER);
} else if (rank.equals("gold")) {
member.setMemberRank(MemberRank.GOLD);
}
こういった場合は、MemberRank に以下のようなファクトリーメソッドを用意するといいです。
public static MemberRank of(String name) {
return MemberRank.valueOf(name.toUpperCase());
}
そうすると、以下のように設定できます。
// rank は、"general", "silver", "gold" の文字列
member.setMemberRank(MemberRank.of(rank));
データに "general" などの文字列ではなく、
一般会員 = 1, シルバー会員 = 2, ゴールド会員 = 3 などのように
数値として持ちたいという事も多いでしょう。
その時は、MemberRankを以下のようにします。
/**
* 会員ランクを表す列挙
*/
enum MemberRank {
/** 一般会員 */
GENERAL(1) {
@Override
public BigDecimal getPrice(BigDecimal catalogPrice) {
// 一般会員は定価のまま
return catalogPrice;
}
},
/** シルバー会員 */
SILVER(2) {
@Override
public BigDecimal getPrice(BigDecimal catalogPrice) {
// 5%引き
BigDecimal discountPrice = catalogPrice.multiply(new BigDecimal("0.05"));
return catalogPrice.subtract(discountPrice);
}
},
/** ゴールド会員 */
GOLD(3) {
@Override
public BigDecimal getPrice(BigDecimal catalogPrice) {
// 10%引き
BigDecimal discountPrice = catalogPrice.multiply(new BigDecimal("0.1"));
return catalogPrice.subtract(discountPrice);
}
};
/** 各列挙が持つ値 */
private int value;
/**
* コンストラクタ
*/
private MemberRank(int value) {
this.value = value;
}
/**
* @param catalogPrice 定価
* @return ランクに応じた金額
*/
public abstract BigDecimal getPrice(BigDecimal catalogPrice);
/**
* ファクトリー
*/
public static MemberRank of(int value) {
return Stream.of(MemberRank.values()).filter(r -> r.value == value)
.findFirst().orElseThrow(IllegalArgumentException::new);
}
}
そうすると、以下のように設定できます。
// rank は、1, 2, 3 の数値
member.setMemberRank(MemberRank.of(rank));
追記2
getPriceのようなメソッドを他にもいくつか追加したい場合、どういったクラス構成が望ましいですか?
会員ランク毎に必要な振る舞いであれば、MemberRankで全て実装すべきです。
例えば以下は、購入金額から付与されるポイントを計算するメソッドと
特別会員かどうかを判断するメソッドを追加してみました。
これは、特別会員だけが見れる項目や、特別会員専用ページなどの制御で使うことを想定しています。
/**
* 会員ランクを表す列挙
*/
enum MemberRank {
/** 一般会員 */
GENERAL(1) {
@Override
public BigDecimal getPrice(BigDecimal catalogPrice) {
// 一般会員は定価のまま
return catalogPrice;
}
@Override
public BigDecimal getPoint(BigDecimal totalPrice) {
// 一般会員は購入金額の1%をポイント付与
BigDecimal percentage = new BigDecimal("0.01");
return totalPrice.multiply(percentage).setScale(0, BigDecimal.ROUND_DOWN);
}
@Override
public boolean isSpecialMember() {
// 一般会員は特別会員でない
return false;
}
},
/** シルバー会員 */
SILVER(2) {
@Override
public BigDecimal getPrice(BigDecimal catalogPrice) {
// 5%引き
BigDecimal discountPrice = catalogPrice.multiply(new BigDecimal("0.05"));
return catalogPrice.subtract(discountPrice);
}
@Override
public BigDecimal getPoint(BigDecimal totalPrice) {
// シルバー会員は購入金額の2%をポイント付与
BigDecimal percentage = new BigDecimal("0.02");
return totalPrice.multiply(percentage).setScale(0, BigDecimal.ROUND_DOWN);
}
@Override
public boolean isSpecialMember() {
// シルバー会員は特別会員でない
return false;
}
},
/** ゴールド会員 */
GOLD(3) {
@Override
public BigDecimal getPrice(BigDecimal catalogPrice) {
// 10%引き
BigDecimal discountPrice = catalogPrice.multiply(new BigDecimal("0.1"));
return catalogPrice.subtract(discountPrice);
}
@Override
public BigDecimal getPoint(BigDecimal totalPrice) {
// ゴールド会員は購入金額の3%をポイント付与
BigDecimal percentage = new BigDecimal("0.03");
return totalPrice.multiply(percentage).setScale(0, BigDecimal.ROUND_DOWN);
}
@Override
public boolean isSpecialMember() {
// ゴールド会員は特別会員
return true;
}
};
/**
* @param catalogPrice 定価
* @return ランクに応じた金額
*/
public abstract BigDecimal getPrice(BigDecimal catalogPrice);
/**
* @param totalPrice 購入合計金額
* @return ランクに応じたポイント数
*/
public abstract BigDecimal getPoint(BigDecimal totalPrice);
/**
* 特別会員かどうかを返す。
* <pre>
* 特別会員とはゴールド会員以上の会員のこと。
* (現状、ゴールド会員が最高ランクだが、来年にはプラチナ会員を導入予定)
* </pre>
*
* @return true..特別会員
*/
public abstract boolean isSpecialMember();
/** 各列挙が持つ値 */
private int value;
/**
* コンストラクタ
*/
private MemberRank(int value) {
this.value = value;
}
/**
* ファクトリー
*/
public static MemberRank of(int value) {
return Stream.of(MemberRank.values()).filter(r -> r.value == value)
.findFirst().orElseThrow(IllegalArgumentException::new);
}
}
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
+4
①と②の延長線上だと思いますが、個人的によく使うのが下記です。
モードなどを指定するメソッド
→int型ではなくEnumを良く使います。
(タイプセーフなメソッドを作れる。言い方は悪いですが、レベルが低いプログラマでも変な書き方をさせないことができます。int型だからといって全く関係のないint型の定数を代入していたりする人も居たり。。。)
データベースを更新するようなメソッドでDBとカラムを指定するためにEnumとEnumMapを利用
・update(DB名.Column名, String value)
└どのDBのカラムをどの値でアップデートするかを指定
→update(EnumMap<DB名.Column名, value>)
└複数のカラムをアップデートするときはこちらを利用
アップデートするための似たようなメソッドが大量にごろごろ転がっていたのですが、これですっきりしました。
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
+2
調べてみたところ、enum
をシングルトンパターンで利用する、というアイデアを見つけました。
http://stackoverflow.com/questions/26285520/implementing-singleton-with-an-enum-in-java
enum
がインスタンス化不可能(かつ継承不可能)であることを利用したもののようです。
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
0
よく使うのは、こういう表現でしょうか?
enum Country {
JAPAN("JP", "JPN"),
;
public final String alpha2;// ISOの2文字 国コード
public final String alpha3; // ISOの3文字 国コード
public Country(String alpha2, String alpha3) {
this.alpha2 = alpha2;
this.alpha3 = alpha3;
}
}
これで、
Country.JAPAN.alpha2.equals("JP"); // true
Country.JAPAN.alpha3.equals("JPN"); // true
と出来ます。
※ iPhoneからの書き込みで面倒なので、アクセッサーメソッドは省略しています(アクセッサーを作ることの是非もありますし)
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
0
SwingのJComboBoxの選択候補にenumを適用したことがあります。簡潔に書けました。
http://d.hatena.ne.jp/torutk/20080105/p1
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
15分調べてもわからないことは、teratailで質問しよう!
- ただいまの回答率 88.33%
- 質問をまとめることで、思考を整理して素早く解決
- テンプレート機能で、簡単に質問をまとめられる
2016/04/06 00:04
ご回答ありがとうございます。
getter、setterからなるDTOクラスに、定数固有メソッドが実装されたプロパティを用意したわけですね。なるほど。素晴らしいです。定数固有メソッドの使いどころがよくわかりました。
一点追加で教えて頂ければと思うのですが、MemberクラスのmemberRankプロパティに、各会員の状態を持たせる場合、どのような実装があり得ますか?
事前に、setMemberRank()で同クラスを引数に設定してあげる感じでしょうか?
2016/04/06 01:32
2016/04/06 12:45 編集
ご回答ありがとうございます。
そうですね、「member.setMemberRank(MemberRank.・・・)」の際に分岐をかけてしまうと、せっかく「getMember().getMemberRank().getPrice(catalogPrice)」でポリモーフィズムした意味が薄れてしまいますからね。
引数に文字列を渡すファクトリーメソッドは、他のサイトに事例が多くあったので覚えていましたが、数値を渡すファクトリーメソッドについては探している最中でした。
(※データベースには通常、区分値として数値が入っているはずなので、このメソッドを使う場合が多そうですね。)
Streamに関しては不勉強ですが、「public static MemberRank of(int value)」では、MemberRankクラスの全ての定数(GENERAL(1)、SILVER(2)、GOLD(3))を走査して、入力データ(1or2or3)に該当するMemberRankオブジェクトが返却されるというわけですね。(4などが入れば例外がスローされる。)
こういった処理は通常、「定数クラス+共通関数クラス」などに書かれそうですが、enumクラス(MemberRank)に集約でき、なおかつそれをDTOクラスにプロパティとして設定してあげるだけで使用可能となるのが非常に便利だと思いました。
■余力があれば・・・
余力があれば教えてほしいのですが、getPriceのようなメソッドを他にもいくつか追加したい場合、どういったクラス構成が望ましいですか?
2016/04/06 13:44
2016/04/13 21:34
ご回答ありがとうございます。(返信が遅れてしまってすみません。。)
なるほど、業務ロジックをゴリゴリ書くわけでなければ、MemberRankクラスで全てのメソッドを書ききったほうがよさそうですね。あまり処理が長くなるようであれば、enumではない通常のインターフェースのデザインパターンを検討するといった感じでしょうか。
本件で一番知りたいところであった、enumでポリモーフィズムを手軽に実現できる具体的な方法を示してくれましたので、ベストアンサーとさせていただきます。
丁寧なご説明ありがとうございました。