オブジェクトへの委譲をstep by stepで。

メモ。

素朴に。

public class Person {
	String name;
	String zipcode;
	String address;
	String phoneNumber;
	
	//getters & setters...
}

郵便番号のチェックは必要だよね。

public class Person {
	String name;
	String zipcode;
	String address;
	String phoneNumber;
	
	public void setZipcode(String zipcode) {
		if (validZipcode(zipcode))  this.zipcode = zipcode;
	}
	
	private boolean validZipcode(String zipcode) {
		//郵便番号形式の確認...
		return false;
	}
	
	//getters & setters...
}

チェックを別に切り出してみたよ

public class Person {
	String name;
	String zipcode;
	String address;
	String phoneNumber;
	
	public void setZipcode(String zipcode) {
		if (ZipcodeValidator.check(zipcode))  this.zipcode = zipcode;
	}
	
	//getters & setters...
}

class ZipcodeValidator {
	static boolean check(String zipcode) {
		//郵便番号形式の確認...
		return false;
	}
}

staticは避けた方が無難

インスタンスの方がテストしやすいから。

public class Person {
	String name;
	String zipcode;
	String address;
	String phoneNumber;
	
	final ZipcodeValidator zipcodeValidator = ZipcodeValidator.instance();
	
	public void setZipcode(String zipcode) {
		if (zipcodeValidator.check(zipcode))  this.zipcode = zipcode;
	}
	
	//getters & setters...
}

class ZipcodeValidator {
	static final ZipcodeValidator instance = new ZipcodeValidator();
	static ZipcodeValidator instance() {
		return instance;
	}
	
	private ZipcodeValidator() {}
	boolean check(String zipcode) {
		//郵便番号形式の確認...
		return false;
	}
}

ZipcodeValidatorはDIで注入すべき

って、この辺りから気力がなくなります。

郵便番号を別クラスのオブジェクトとして扱う。

public class Person {
	String name;
	Zipcode zipcode;
	String address;
	String phoneNumber;
	
	public void setZipcode(String zipcode) {
		this.zipcode = new Zipcode(zipcode);
	}
	
	//getters & setters...
}

class Zipcode {
	final String code;
	
	Zipcode(String code) {
		if(valid(code)) {
			this.code = code;
		} else {
			throw new IllegalArgumentException(code + " is illegal.");
		}
	}
	
	boolean valid(String zipcode) {
		//郵便番号形式の確認...
		return false;
	}
}

newを避ける。

不変な値クラス(value class)なので、別オブジェクトを作る必要はない。国際化にも対応しやすい(たぶん)。
Eclipseなら、「refactor」の「Introduce Factory...」で一発。はい、「それEclipseでできるし」。

public class Person {
	String name;
	Zipcode zipcode;
	String address;
	String phoneNumber;
	
	public void setZipcode(String zipcode) {
		this.zipcode = Zipcode.valueOf(zipcode);
	}
	
	//getters & setters...
}

class Zipcode {
	public static Zipcode valueOf(String code) {
		return new Zipcode(code);
	}

	final String code;
	
	private Zipcode(String code) {
		if(valid(code)) {
			this.code = code;
		} else {
			throw new IllegalArgumentException(code + " is illegal.");
		}
	}
	
	boolean valid(String zipcode) {
		//郵便番号形式の確認...
		return false;
	}
}

FAQ

  • Zipcodeクラスに切り出す利点は?
    • 高凝集と低結合が実現できる。Validatorパターンだと、住所や名前、生年月日など、Personが持つ他の要素にも同様にバリデーションが必要となった場合に、依存関係が増え、高結合となる。
  • バリデーションが必要なメンバが少なければ、バリデータでも問題ないのでは?
    • まぁそうです。実際に複雑になってから変更するという手も十分あり。
  • PersonValidatorとしてまとめればいいのでは?
    • PersonValidator内の各validateメソッドはお互いに関連が薄いので、まとめる理由がない。理由がないのにまとめるのは悪(util.Constantsクラスで全定数をまとめるデメリットを想像できる?)。
  • 互いに関連するメンバのバリデーションはどうする?
    • 例えば住所と郵便番号の関連性であれば、addressのメンバとしてzipcodeを管理するようにし、addressオブジェクト内で関連性をチェックすべき。DTOと構成がマッチしにくい…かも知れないが、PersonテーブルとAddressテーブルを分ければ問題はない。とはいえ現実的じゃない部分もあるので、Personクラス内でチェックさせるよう妥協すればよい。他、Person固有の条件であると考えられれば、普通にPersonクラス内でチェックさせる。
  • Zipcodeクラス内でバリデータを別に分けた方がよいのでは?
    • んー、どうなんだろ。複数のメンバを持つオブジェクトなら、分けない方が簡単かも知れない。