はじめに

バックエンドエンジニアのロードマップに沿ってエンジニアとしての自己肯定感を養うシリーズです。

現場で役立つシステム設計の原則を元に記事を作成しています。

設計パターン

早期リターン

複雑になりがちな場合分けのロジックの見通しをよくしようというものです。

ありがちなif-elseをつなげた(例 1)

Yen fee() {
  Yen result;

  if (isChild()) {
    result = chidFee();
  } else if (isSenior()) {
    result = seniorFee();
  } else {
    result = adultFee();
  }
  return result;
}

さっきのコードからローカル変数を抜いて結果をすぐにreturnするようにした(例 2)

Yen fee() {
    if (isChild()) {
    	return chidFee();
    } else if (isSenior()) {
    	return seniorFee();
    } else {
    	return adultFee();
    }
}

このように、値が決まるとすぐにreturnするやり方を早期リターンと言います。

ガード節

上記の例 2 からelseを抜いた(例 3)

Yen fee() {
    if (isChild()) return chidFee();
    if (isSenior()) return seniorFee();

    return adultFee();
}

elseを抜いた早期リターンをガード節と言います。非常にコンパクトですね。

単文の並びに変えることで、独立性が高くなります。また、単文同士が結合していない(疎結合)ので追加が容易です。

多態

それぞれのクラスを包括するようなクラス(インターフェース)を作ることで、使う側のクラス、メソッドはインターフェースさえ実装していれば、それがどのクラスでも気にする必要がありません。

インターフェースを用意する(例 4)

interface Fee {
  Yen yen();
  String label();
}

// 大人クラス
class AdultFee implements Fee {
  Yen yen() {
    return new Yen(1000);
  }

  String label() {
    return "大人";
  }
}

// 子クラス
class ChildFee implements Fee {
  Yen yen() {
   return new Yen(50)
  }

  String label() {
   return "子供";
  }
}

使う側(例 5)

class Reservation {
  List<Fee> fees; // 大人と子供のリスト

  Reservation() {
    fees = new ArrayList<Fee>();
  }

  // Feeはインターフェースなので、大人と子供の両方に使える
  void addFee(Fee fee) {
    fees.add(fee);
  }

  // 大人と子供の合計料金
  Yen feeTotal() {
    Yen total = new Yen();

    for (Fee fee : fees) {
      total.add(fee.yen());
    }
    reuturn total;
  }
}

インターフェースを使うことで、大人と子供の料金クラスをまとめて処理できました。

if 文を使わずに済むと見通しがよいですね。

このようにインターフェースを使用して、異なるクラスのオブジェクトを同じ型として扱う仕組みを多態と言います。

多態にすることでFeeのインターフェースを実装した別のクラス(シニアクラスなど)を追加してもReservationクラスを変更することはありません。改修箇所が少なくなるのは大きなメリットです。

列挙型

多態は同じインターフェースを実装したクラスの一覧が分かりにくくなる場合がありますclass宣言のimplementsを見れば一目瞭然ですが、一つ一つ見ていくのに時間がかかります。

列挙型を使うことで区分(インターフェースのグループ)の一覧を作成することができます。

列挙型を使って料金区分ごとのロジックを表現(例 6)

enum FeeType {
  adult(new AdultFee());
  child(new ChidFee());
  senior(new SeniorFee());

  private Fee fee;

  private FeeTypt(Fee fee) {
    this.fee = fee;
  }

  Yen yen() {
    return fee.yen();
  }

  String label() {
    return fee.label();
  }
}

使う側(例 7)

Yen feeFor(Stirng feeTypeName) {
  FeeType feeType = FeeType.valueOf(feeTypeName);

  return feeType.yen();
}

enumクラスのvalueOf()メソッドはMapget()メソッドと同様に、if 文を使うことなく料金区分ごとのオブジェクトを取得できます。

このような振る舞いを持った列挙型(enum)を区分オブジェクトと言います。

まとめ

前回同様、コードをスッキリさせる手法を見てきました。インターフェースを使うことで抽象的なメソッドを作ったり、列挙型を使って if 文をなるべく減らせることはコードの保守性、拡張性を高めてくれますね。

備考

現場で役立つシステム設計の原則

表紙イラスト:Loose Drawing