読者です 読者をやめる 読者になる 読者になる

Status Code 303 - See Other

Java, C, C++, C#, Objective-C, Swift, bash, perl, ruby, PHP, Python, Scala, Groovy, Go, DevOps, Raspberry Pi など。情報の誤りや指摘・意見などは自由にどうぞ。

各ケースのデータ選択には分岐より規約に基づいた処理を適用する

Java コード改善

概要

分岐処理は、手軽に記述できるため、多くの場面で利用される。

しかし、あるデータの値によって、途中のデータを選択する処理を分岐を用いて
実装してしまうと、全てのケースをカバーしなければならないため、
新たなケースの追加や既存のケースの修正によって、プログラムを修正する必要がある。

もし、これら各ケースをプログラムでは同じ処理だと認識できれば、
ケースの追加やケースの修正を感知しないため、もっと保守性を向上できる。
今回はこれについて説明する。

詳細説明

今回は、会員のカテゴリ(VIP, 会員, 無料会員)があり、
これらのカテゴリ毎にメッセージを別々に表示したいとしよう。
まずは、改善の余地がある処理を提示する。

package sample;

import resources.properties.Property;

public class Main {

	public static void main(String[] args) {
		
		Property property = new Property("message");
		String title, message;
		
		// ここで会員カテゴリを取得するとする.
		String category = getMemberCategory();
		// ここでデータを選択する.
		if(category.equals("A")) {
			title = property.getString("vip.title");
			message = property.getString("vip.message");
		}else if(category.equals("B")) {
			title = property.getString("member.title");
			message = property.getString("member.message");
		}else if(category.equals("C")) {
			title = property.getString("normal.title");
			message = property.getString("normal.message");
		}else {
			throw new IllegalStateException("カテゴリ名が不正です");
		}
		// データを用いた処理.
		System.out.println("title: " +  title);
		System.out.println("message: " +  message);
	}
	// コンパイラを通すために仮の値を作成.
	private static String getMemberCategory() {
		return "B";
	}
}

なお、property クラスは、message.properties からキーに対応した値を取得するクラスである。
以下の過去記事にその実装例がある。
プロパティファイルの使用方法 - Status Code 303 - See Other
コンストラクタでリソースファイル名を指定できる実装にしているが、基本的に動作は同じ。

そして、タイトルやメッセージを格納した message.properties はこちら。

vip.title = VIP会員様へ
vip.message = 今お買い物頂くと 40% のポイントが溜まります。
member.title = 会員様へ
member.message = 今お買い物頂くと 20% のポイントが溜まります!
normal.title = 無料会員様へ
normal.message = 正会員になれば、ポイントが溜まり、1p=1円でご利用できます!

タイトルとメッセージを外出しファイルにすることで、内容の変更は properties ファイルの
更新だけになり、プログラムを修正する必要がないようにしている。

しかし、もうちょっと保守性を向上できる。
今回、ネックとなっているのは分岐の部分だ。

分岐を用いた、このような実装は自然に映るかもしれないが、
会員カテゴリが追加、変更された場合は、以下を変更しなければならない。

  • プログラム(if 文の分岐部分)
  • リソースファイルの xxx.title, xxx.message キーの追加

本来、プログラムは処理を記述するものであるから、データ追加の影響は受けたくない。
タイトルとメッセージだけを追加すれば動作するようにしたい。

これを実現するには、下記のように message.properties のキー名を
カテゴリに対応したキー名にし、それを処理で取得するようにする。
今回の例では、次のようにする。

  • [カテゴリ名] + ".title" の値をタイトルに表示する
  • [カテゴリ名] + ".message" の値を本文に表示する

このように、リソースデータの記述方法を規約化してしまうことによって、
各ケースに対する処理をまとめてしまう。

package sample;

import java.util.MissingResourceException;

import resources.properties.Property;

public class Main {

	public static void main(String[] args) {
		
		Property property = new Property("message");
		String title, message;
		
		// ここで会員カテゴリを取得するとする.
		String category = getMemberCategory();
		try {
			// ここでデータを選択する.
			title = property.getString(category + ".title");
			message = property.getString(category + ".message");

		} catch(MissingResourceException e) {
			throw new IllegalStateException("カテゴリ名が不正です");
		}
		// データを用いた処理.
		System.out.println("title: " +  title);
		System.out.println("message: " +  message);
	}
	// コンパイラを通すために仮の値を作成.
	private static String getMemberCategory() {
		return "B";
	}
}

message.properties は以下のようにキー名を変更する。

# 存在するカテゴリ名 + .title, + .message をデータとして取得する
A.title = VIP会員様へ
A.message = 今お買い物頂くと 40% のポイントが溜まります。
B.title = 会員様へ
B.message = 今お買い物頂くと 20% のポイントが溜まります!
C.title = 無料会員様へ
C.message = 正会員になれば、ポイントが溜まり、1p=1円でご利用できます!

このようにすれば、もし、カテゴリが追加されたとしても、message.properties に
新しいカテゴリ "D" に対する title と message を追加すれば、処理は変更しなくて良くなる。

# 存在するカテゴリ名 + .title, + .message をデータとして取得する
A.title = VIP会員様へ
A.message = 今お買い物頂くと 40% のポイントが溜まります。
B.title = 会員様へ
B.message = 今お買い物頂くと 20% のポイントが溜まります!
C.title = 無料会員様へ
C.message = 正会員になれば、ポイントが溜まり、1p=1円でご利用できます!
# 追加
D.title = お試し会員様へ
D.message = お試し期間中は全サービスが使えますが、有効期間は1週間です!

その出力結果。(getMemberCategory() で "D" を返すように変更)

title: お試し会員様へ
message: お試し期間中は全サービスが使えますが、有効期間は1週間です!

今回は、簡単のため、同一 properties ファイル内にデータを記述しているが、
カテゴリによって変更されるデータの数が多くなればなるほど、大量のデータで混沌とする。

この場合は、読み込むリソースファイル自体を変更する。
例えば カテゴリが "A" の場合、message_A.properties を読み込むようにする。

注意点として、この方法は各ケースの異なる部分が処理データだけだということ。
もし、各ケースの処理自体が異なっている場合は、単純にこの方法は適用できない。

もし、データ選択とそのデータの処理がコード的に分割できるのであれば、
まずそれを行ってから、上記変形を行う。

結論

データの値によって、処理されるデータを変更したい場合は、
リソースファイルに規約を適用した処理を記述することによって、
ケースの追加や変更にリソースファイルだけで対応できるようになる。

しかし、各ケースによって処理自体が異なってしまう場合は、単純に適用できない。
もし、これらからデータ選択部と処理部が分割できるなら、それをまず行う。