テスト・コードと行き当たりバッタリ設計の実例(その2)

その1(http://foobardam.hatenablog.com/entry/2012/12/24/060705)のつづき。

JavaBeansを全く知らない人向けのBeanの概要説明

本記事はテスト・コードと設計の説明が中心的な話題の予定なのだが、題材としてBeanの例を進めるのでBeanの話が多くなると思う。そのためJavaBeansが全く分からないという人は、これからの説明についてくるのが困難になることが予想される。ここではざっくりと説明しておく。
たとえば、以下のようなPersonクラスがあるとする。

public class Person {
	private String name;
	private int age;
	private String tel;
	
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	public String getTel() {
		return tel;
	}
	public void setTel(String tel) {
		this.tel = tel;
	}
}

クラスPersonは、プロパティ(name、age、tel)を持つBeanであるという。
JavaBeansには、Beanであることの厳密な必要条件があるのだけど、本筋から外れるのでここでは省略する。いずれ説明するときがあるかもしれない。実は、このPersonクラスはJavaBeansの必要条件を満たしていない*1ので、厳密にはBeanといいずらいのだが、そういった厳密さは目をつぶっていただきBeanとして扱うことにする。
詳細が知りたい人は、http://ja.wikipedia.org/wiki/JavaBeansを参照と書きたいところだけど、本当にJavaBeans初めてだと何が何だか分からないと思う。JavaBeansが出てきた背景が分からないと理解しにくいものがある。昔の自分がそうだった。
Beanとは部品であるという説明を受けることもあるが、ここではデータベースに入るエンティティ・データみたいなものだと思って欲しい。

Personの内容(プロパティ)を出力してみる

では、早速、Personの内容を標準出力に出してみる。

public class Person {
	private String name;
	private int age;
	private String tel;
	
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	public String getTel() {
		return tel;
	}
	public void setTel(String tel) {
		this.tel = tel;
	}
	public static void main(String... args){
		System.out.println(new Person());
	}
}

って、Personクラスにmainメソッドをつけただけ。
結果は、

Person@10b30a7

となった。Personオブジェクトのハッシュ値が表示された。
当然、Personクラスに

@Override
public String toString() {
	return "Person [name=" + name + ", age=" + age + ", tel=" + tel + "]";
}

のようにtoStringメソッドをオーバーライドして実行してあげれば

Person [name=null, age=0, tel=null]

のようにBeanのプロパティが見える結果になる。
ちなみに、このtoStringメソッドのオーバーライドは、Eclipseに入っていたソースコード支援機能を用いて半自動で作成した。

Personクラスをもう少し便利にする

ここで、オブジェクトを生成してから、いちいちset系メソッドでプロパティをセットするのが少し面倒である。その必要がないように便宜をはかる。引数でプロパティを指定できるコンストラクタを定義する。しかし、JavaBeansとして必要なのが、引数なしコンストラクタである。これがないと、JavaBeansの要件を満たさないのである*2。ここでは、引数なしコンストラクタは、呪文なようなものとしてとにかくつけておこう。本連載で困るようなことはないかもしれないが、Beanに引数なしコンストラクタを用意するのは良い習慣であると思う。

public class Person {
	private String name;
	private int age;
	private String tel;
	
	public Person(){}
	public Person(String name, int age, String tel) {
		super();
		this.name = name;
		this.age = age;
		this.tel = tel;
	}

	// getter ... setter (省略)

	@Override
	public String toString() {
		return "Person [name=" + name + ", age=" + age + ", tel=" + tel + "]";
	}
	
	public static void main(String... args){
		System.out.println(new Person());
		System.out.println(new Person("太郎", 28, "090-1234-5678"));
	}
}

実行結果は、

Person [name=null, age=0, tel=null]
Person [name=太郎, age=28, tel=090-1234-5678]

少し脱線するが大事なことなので少し説明しておく。Javaの基礎中の基礎であるが、コンストラクタを明示的に定義しないとデフォルト・コンストラクタ(引数なしのコンストラクタ)が暗黙的に設定される。今回のように引数ありのコンストラクタを新たに定義してしまうと、デフォルト・コンストラクタ(引数なしのコンストラクタ)が設定されなくなってしまうため、JavaBeansの要件を満たすためには明示的に引数なしコンストラクタを記述する必要が生じる。JavaBeansをきちんと利用したい場合、このことを知らないと最初にハマる罠なので気をつけよう。

ここまでの成果

https://github.com/foobardam/SpyPrinter/tree/0eff9580f5159cd201e23b6d2f9d6ac0d7dfa1ef
ちなみに余談だが、本記事はWindowsで開発しているので、当初文字コードSJISで書いていた。しかし、githubでブラウザにてコードを閲覧すると文字化けするので、ソースコードutf-8で開発することにした*3

次回

今回はその1でも書いたが、toStringメソッドのオーバーライドは、(Eclipseの支援機能があるとはいっても)クラスごとに用意しなければならなかったり、プロパティが増える度に作り直さなければならない。つまり、汎用性がない。今回の目的は、汎用的に使えるprintメソッドを作ることとする。では、どうするのか?標準で入っているJDKjava.beansパッケージとjava.lang.refrectパッケージ配下にあるクラスを駆使するというのが直接的な答えになるのだが、そこまで解説していると今回のテーマからあまりにも脱線してしまう。よって、apache commonsのBeanUtilsライブラリを導入することとする。次回は、BeanUtilの導入からとしよう。

*1:直列化とか考えるとややこしくなる

*2:引数なしコンストラクタがJavaBeansの要件になっている理由をここでは説明しない。GUIのデザイナなどBeansを部品として扱うツールのときに問題が起きたりする

*3:この辺は少しだけ記事にした→http://foobardam.hatenablog.com/entry/2012/12/23/030351