1. 仕様

顧客が借りたビデオのレンタル料金を計算して計算書を印刷する。

  • ビデオレンタルの料金を計算して計算書を印刷するプログラム

  • システムにはどの映画を何日間借りるかが入力される。

  • 貸出の日数によって料金が計算され、映画の分類が判定される。

  • 映画の分類は3つある。一般向け、子供向け、新作。

  • レンタルポイントも印刷される。新作かどうかによってポイント計算の仕方が異なる。

2. 設計

2.1. TODOリスト

  • ✓ スモークテスト作成

  • ✓ statementメソッドの分割、再配置

  • ✓ amountForメソッドの移動

  • ✓ レンタルポイント計算部分の抽出

  • ✓ 一時変数の削除

  • ✓ 料金計算の条件文をポリモーフィズムに置き換える

2.2. ユースケース図

diag 48441ede18c6840e39c6ddbcd8366d02

2.3. クラス図

diag ba8423211fe68d04afb0048b976bfae3

2.4. シーケンス図

diag 018309fe98a870ba01ced1f8d4c8f541

3. 開発

3.1. Price.java

package rental.domain;

abstract class Price {

  abstract int getPriceCode();

  abstract int getCharge(int daysRented);

  int getFrequentRenterPoints(int daysRented) {
    return 1;
  }
}

3.2. RegularPrice.java

package rental.domain;

public class RegularPrice extends Price {

  @Override
  int getPriceCode() {
    return Movie.REGULAR;
  }

  int getCharge(int daysRented) {
    int result = 200;
    if (daysRented > 2) result += (daysRented - 2) * 150;
    return result;
  }
}

3.3. ChildrenPrice.java

package rental.domain;

public class ChildrenPrice extends Price {

  @Override
  int getPriceCode() {
    return Movie.CHILDREN;
  }

  int getCharge(int daysRented) {
    int result = 150;
    if (daysRented > 3) result += (daysRented - 3) * 150;
    return result;
  }
}

3.4. NewReleasePrice.java

package rental.domain;

public class NewReleasePrice extends Price {

  @Override
  int getPriceCode() {
    return Movie.NEW_RELEASE;
  }

  int getCharge(int daysRented) {
    return daysRented * 300;
  }

  int getFrequentRenterPoints(int daysRented) {
    return (daysRented > 1) ? 2 : 1;
  }
}

3.5. Movie.java

package rental.domain;

public class Movie {
  public static final int CHILDREN = 2;
  public static final int REGULAR = 0;
  public static final int NEW_RELEASE = 1;

  private String _title;
  private Price _price;

  public Movie(String title, int priceCode) {
    _title = title;
    setPriceCode(priceCode);
  }

  public int getPriceCode() {
    return _price.getPriceCode();
  }

  public void setPriceCode(int arg) {
    switch (arg) {
      case REGULAR:
        _price = new RegularPrice();
        break;
      case CHILDREN:
        _price = new ChildrenPrice();
        break;
      case NEW_RELEASE:
        _price = new NewReleasePrice();
        break;
      default:
        throw new IllegalArgumentException("不正な料金コード");
    }
  }

  public String getTitle() {
    return _title;
  }

  public int getCharge(int daysRented) {
    return _price.getCharge(daysRented);
  }

  public int getFrequentRenterPoints(int daysRented) {
    return _price.getFrequentRenterPoints(daysRented);
  }
}

3.6. Rental.java

package rental.domain;

public class Rental {
  Movie _movie;
  private int _daysRented;

  public Rental(Movie movie, int daysRented) {
    _movie = movie;
    _daysRented = daysRented;
  }

  public int getDaysRented() {
    return _daysRented;
  }

  public Movie getMovie() {
    return _movie;
  }

  int getCharge() {
    return _movie.getCharge(_daysRented);
  }

  int getFrequentRenterPoints() {
    return _movie.getFrequentRenterPoints(_daysRented);
  }
}

3.7. Customer.java

package rental.domain;

import java.util.ArrayList;
import java.util.List;

public class Customer {
  private String _name;
  private List<Rental> _rentals = new ArrayList<>();

  public Customer(String name) {
    _name = name;
  }

  public void addRental(Rental arg) {
    _rentals.add(arg);
  }

  public String getName() {
    return _name;
  }

  public String statement() {
    String result = getName() + "様のレンタル明細" + "\n";
    for (Rental each : _rentals) {
      //この貸し出しに関する数値の表示
      result += "\t" + each.getMovie().getTitle() + "\t" + each.getCharge() + "円\n";
    }

    //フッタ部分の追加
    result += "合計金額 " + getTotalAmount() + "円\n";
    result += "獲得ポイント " + getFrequentRenterPoints() + "ポイント";
    return result;
  }

  public String htmlStatement() {
    String result = "<H1><EM>" + getName() + "</EM>様のレンタル明細</H1>\n";
    for (Rental each : _rentals) {
      //この貸し出しに関する数値の表示
      result += each.getMovie().getTitle() + ": " + each.getCharge() + "円<BR>\n";
    }

    //フッタ部分の追加
    result += "<P>合計金額 <EM>" + getTotalAmount() + "円</EM></P>\n";
    result +=
      "<P>獲得ポイント <EM>" + getFrequentRenterPoints() + "ポイント</EM></P>";
    return result;
  }

  private int getFrequentRenterPoints() {
    int frequentRenterPoints = 0;
    for (Rental each : _rentals) {
      frequentRenterPoints += each.getFrequentRenterPoints();
    }
    return frequentRenterPoints;
  }

  private int getTotalAmount() {
    int totalAmount = 0;
    for (Rental each : _rentals) {
      totalAmount += each.getCharge();
    }
    return totalAmount;
  }
}

3.8. StatementOutput.java

package rental;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import rental.domain.Customer;
import rental.domain.Movie;
import rental.domain.Rental;
import static org.junit.jupiter.api.Assertions.assertEquals;

public class StatementOutput {
  Rental newRelease;
  Rental children;
  Rental regular;

  @BeforeEach
  void setupData() {
    Movie newReleaseMovie = new Movie("新作", Movie.NEW_RELEASE);
    Movie childrenMovie = new Movie("子供", Movie.CHILDREN);
    Movie regularMovie = new Movie("一般", Movie.REGULAR);

    newRelease = new Rental(newReleaseMovie, 3);
    children = new Rental(childrenMovie, 2);
    regular = new Rental(regularMovie, 1);
  }

  String textOutput;
  String htmlOutput;

  @BeforeEach
  void setupResult() {
    textOutput =
      "山田様のレンタル明細\n" +
        "\t新作\t900円\n" +
        "\t子供\t150円\n" +
        "\t一般\t200円\n" +
        "合計金額 1250円\n" +
        "獲得ポイント 4ポイント";

    htmlOutput =
      "<H1><EM>山田</EM>様のレンタル明細</H1>\n" +
        "新作: 900円<BR>\n" +
        "子供: 150円<BR>\n" +
        "一般: 200円<BR>\n" +
        "<P>合計金額 <EM>1250円</EM></P>\n" +
        "<P>獲得ポイント <EM>4ポイント</EM></P>";
  }

  @Test
  @DisplayName("ステートメント出力")
  void textOutput() {
    Customer customer = new Customer("山田");
    customer.addRental(newRelease);
    customer.addRental(children);
    customer.addRental(regular);

    assertEquals(textOutput, customer.statement());
  }

  @Test
  @DisplayName("HTMLステートメント出力")
  void htmlOutput() {
    Customer customer = new Customer("山田");
    customer.addRental(newRelease);
    customer.addRental(children);
    customer.addRental(regular);

    assertEquals(htmlOutput, customer.htmlStatement());
  }
}

4. 参照