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

アラタナエンジニアブログ

aratana Engineer's Blog

ラップは無駄に見えるが役に立つ♡ 〜オブジェクト指向エクササイズ ルール3と8〜

DDD(ドメイン駆動設計) オブジェクト指向

この記事はアラタナアドベントカレンダー14日目の記事です。

f:id:kimesawa:20161202085600j:plain

YO♫YO♫YO♫余貴美子〜
カラオケは好きだけど、ラップは全くできない木目沢です。

以前、オブジェクト指向エクササイズというのを紹介しました。
lab.aratana.jp

そのなかの

ルール3:すべてのプリミティブ型と文字列型をラップすること
ルール8:ファーストクラスコレクションを使用すること

って具体的にどういうこと?というが今回のテーマです。

オブジェクト指向らしく書くために

まず、以下の課題に取り組んでみてください。

【課題①】
あるECサイトの会員登録の機能を作成してください。
会員の情報として

  • 名前
  • 年齢
  • 住所

が必要です。

単純にぱぱっと書くとこんな感じになるでしょうか?

String name;
int age;
String postCode;
String address;

name = "アラタナ 太郎";
age = 30;
postCode = "880-0000";
address = "宮崎県宮崎市"

memberRegist( name, age, postCode, address );

とても普通のコードに見えますが、これでは、memberRegistというメソッド名でなんとか会員登録っぽいことがわかりますが、
名前や住所が「会員」のものなのかがわかりずらいですね。

そこで、クラスに抽出してみましょう。

class Member{
  String name;
  int age;
  String postCode;
  String address;
}

Member member = new Member( "アラタナ 太郎", 30, "880-0000",  "宮崎県宮崎市" );
memberRegist( member );

※ コンストラクタ等、諸々省略しています。

「会員」というのがはっきり明記できて少しすっきりしました。
ただ、姓名の間の空白とか、郵便番号の形式とかこれだと自由すぎてはっきりしませんね。

class Member{
  String familyName;
  String firstName;

  int age;

  String postCodeDistrict;
  String postCodeTownArea;

  String address;

  String fullName(){
      return String.format( "%s %s", this.familyName, this.firstName  );
  }

  String postCode(){
      return String.format("%s-%s", this.postCodeDistrict, this.postCodeTownArea );
  }
}

Member member = new Member( "アラタナ",  "太郎", 30, "880", "0000",  "宮崎県宮崎市" );
memberRegist( member );

※ コンストラクタ等、諸々省略しています。

これで、名前や郵便番号の形式が整いました。

さらにこんな要件追加はどうでしょう。

【課題②】

  • 年齢に「歳」つきで表示する機能を追加してください。
  • 郵便番号が存在するものかチェックする機能を追加してください。
  • 県だけを表示する機能を追加してください。
  • 県が存在するものかチェックする機能を追加してください。
  • 名前の形式を半角空白ではなく全角空白にしてください。
  • ・・・

いかがでしょうか?これを全てMemberクラスに書きますか?

こうしてみましょうか。

  class Member{
     Name name;
     Age age;
     PostCode postCode;
     Address address;
  }

  class Name{
     String familyName;
     String firstName;

     String fullName(){
        return String.format( "%s %s", this.familyName, this.firstName  );
      }
  }

  class Age{
      int age;

      String ageAsText(){
         return String.format( "%d歳", this.age );
      } 
  }

  class PostCode{
     String postCodeDistrict;
     String postCodeTownArea;

     String postCode(){
       return String.format("%s-%s", this.postCodeDistrict, this.postCodeTownArea );
     }
  
    Boolean isExist(){
       ・・・
    }
  }

  enum Prefecture{
    福岡県, 佐賀県, 長崎県, 大分県, 熊本県, 宮崎県, 鹿児島県 

    Boolean isExist( Prefecture prefecture ){
      ・・・
    }
  }

  class Address{
     Prefecture prefecture;
     String city;
  }
※ コンストラクタ等、諸々省略しています。

いかがでしょうか?
名前に関する処理は名前クラスに、年齢に関する処理は年齢クラスに収まることですごく読みやすくなりました。
それに、会員クラスを見て下さい。
int や String がなくなって、会員は名前(型)、年齢(型)、郵便番号(型), 住所(型)の情報を持つということがよりはっきり分かるようになりましたね。

intやdobleなどのプリミティブ変数やStringをラップするというのはこういうことなんです。
これを徹底することで、オブジェクト指向らしいコードが書けるようになります。
もちろん、姓・名や郵便番号の地域コードやエリアコードなんかも別クラスにしてみて下さい。

以前、こんなエントリーを書きました。

lab.aratana.jp

(オブジェクト指向設計では)機能ではなく、部品と役割を考えることが基本になります。

名前、年齢、郵便番号といった部品、そして、名前の形式は名前の役割、年齢の書式は年齢の役割、郵便番号の存在チェックは郵便番号の役割ということですね。

このエクササイズを通して、オブジェクト指向の考え方が自然に身につきます。
ぜひ、実践してみて下さい。

ファーストクラスコレクション

【課題③】

  • 会員一覧の機能を追加して下さい。
  • 会員数を計算する機能を追加して下さい。
  • ・・・

このような課題はどうでしょうか?
普通に考えればこんな感じでしょうか?

  List<Member> members;
  
  ・・・メンバーを抽出してmembersに追加
  // 会員数
  return member.size()

正しいソースコードですよね。しかし、これでは会員一覧がどのような特性を持っていて、どのような役割があるのかがわかりません。うっかり後任の担当がclearなんて書いてしまったら目も当てられません。

そこでこんな風にします。

class Members{
  List<Member> members;

  Integer numberOfMembers(){
     return members.size();
  }
}

これでうっかりclearされる心配もありませんし、会員数を表すのにただの"size()"ではなく"会員数(numberOfMembers)"というきちんと意図を持ったメソッドも表現できました。

このように単にコレクションをそのまま使うのではなく、コレクションをラップしたクラスを用意することでプリミティブ変数のラップと同じような効果を得ることができます。

この表現を"ファーストクラスコレクション"といいます。

この2つのルールでオブジェクト指向の設計がより理解できるようになるはずです。ぜひ試してみてください。

f:id:kimesawa:20161202085600j:plain

次回のアドベントカレンダーはマーケチームによる「マーケエンジニアが書くAMP対応!」です。
話題のAMP!エンジニア的には肝を冷やしていますが、、、面白い記事を期待しています\(^o^)/

ではでは^^