導入
こんにちは、もんすんです。
Javaプログラミングでは、インタフェースは通常、仕様を定義するために使用されます。
これについては、一度こちらの記事で触れましたね。
クラスがインタフェースを実装することで、そのクラスのインスタンスが特定のメソッドを持つことを保証し、利用者にどのような操作が可能であるかを伝えます。
しかし、インタフェースを本来の目的とは異なる用途で使われることが時々あります。
例えば定数を提供するために使用することを「定数インタフェース」と呼び、これはインターフェースのアンチパターンです。
今回は、その問題点と正しい代替手法について解説します。
本題
定数インタフェースとは
以下のようなコードを考えていきます。
// 定数インタフェース(使ってはいけない例)
public interface NumberConstants {
int SEN_NUMBER = 1000;
int HYAKU_MAN_NUMBER = 1000000;
int JU_OKU_NUMBER = 1000000000;
}
このインタフェースは、1,000
、1,000,000
、1,000,000,000
を定数として定義するためだけに作られています。
そして、定数を利用するクラスで次のように実装されます。
public class CalculateClass implements NumberConstants {
public void print5sen() {
System.out.println("5千は数字で、" + SEN_NUMBER * 5);
}
}
これは便利ではありますが、問題点を有しています。
なぜ「定数インタフェース」は問題なのか?
1. 本来の用途に反している
インタフェースは仕様を定義するために存在します。
定数を定義するだけのためにインタフェースを使用することは、設計意図に反しており、コードの可読性を下げます。
2. 必要のない定数の公開
クラスが定数インタフェースを実装すると、実際には内部実装でしか必要のない定数がクラスのAPIとして公開されます。
これは不要な情報を利用者に晒すことになり、混乱を招きます。
3. 柔軟性の損失
定数インタフェースを実装すると、将来的にその定数が不要になっても、バイナリ互換性を保つためにインタフェースを削除できなくなります。
4. 名前空間の汚染
final
ではないクラスが定数インタフェースを実装すると、そのサブクラスでもインタフェース内の定数が利用可能になります。
これにより、名前空間が汚染され、意図しない定数が使われるリスクが生まれます。
代替手法
では、定数を定義する場合はどうすればよいのでしょうか?
以下に3つの推奨される方法を紹介します。
1. 既存のクラスに定数を追加
定数が特定のクラスやインタフェースに強く結び付くのであれば、そのクラスやインタフェースに直接定数を追加することができます。
public class ConstantsClass {
public static final int SEN_NUMBER = 1000;
}
2. enum
を使う
もし定数が関連性の高いグループを形成している場合は、enum
を利用するのも良い選択です。
public enum NumberConstants {
SEN_NUMBER(1000, "thousand");
HYAKU_MAN_NUMBER(1000000, "million");
JU_OKU_NUMBER(1000000000, "billion");
private final int value;
private final String name;
PhysicalConstant(int value, String name) {
this.value = value;
this.name = name;
}
public int getValue() {
return value;
}
public String getName() {
return name;
}
}
これにより、定数に付随する名前や単位などの情報も簡単に管理することが可能になります。
3. ユーティリティクラスを使う
順番が最後になってしまいましたが、最も一般的な方法は、定数を保持するためのインスタンス化不可能なユーティリティクラスを作成することです。
ユーティリティクラスについてはこちらをご参照ください。
// 定数ユーティリティクラス
public class NumberConstants {
// privateコンストラクタでインスタンス化させない(上記記事参考)
private NumberConstants() {}
public static final int SEN_NUMBER = 1000;
public static final int HYAKU_MAN_NUMBER = 1000000;
public static final int JU_OKU_NUMBER = 1000000000;
}
利用する際は、次のようにユーティリティクラスのクラス名を付けて定数を参照します。
public class CalculateClass {
public void print5sen() {
System.out.println("5千は数字で、" + NumberConstants.SEN_NUMBER * 5);
}
}
Staticインポート
でさらに簡潔に
ユーティリティクラスの定数を頻繁に使う場合、import static(Staticインポート)を使用するとコードをより簡潔にできます。
import static com.hoge.constants.NumberConstants.*;
public class CalculateClass {
public void print5sen() {
System.out.println("5千は数字で、" + SEN_NUMBER * 5);
}
}
このようにすることで、一つ前のコードでは宣言していたユーティリティクラス名を省略することができます。
ただし、Staticインポートを多用すると、どのクラスから定数が来ているのか分かりにくくなるため、必要最小限に留めた方が安全です。
最後に
今回はアンチパターンである定数インターフェースの紹介と、その解決方法について触れてみました。
インタフェースは仕様を定義するために使うべきであり、定数を提供するために使うべきではありません。今回紹介したの方法を使うことで、可読性が高く保守しやすいコードを書いていきましょう!