オブジェクトへの委譲を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クラス内でバリデータを別に分けた方がよいのでは?
- んー、どうなんだろ。複数のメンバを持つオブジェクトなら、分けない方が簡単かも知れない。