본문 바로가기
Study/Refactoring

리팩토링 챕터 8

by irerin07 2023. 7. 25.
728x90

8장 기능 이동

8.1 함수 옮기기

배경

  • 좋은 소프트웨어 설계의 핵심은 모듈성이다
    • 모듈성이 높으면 프로그램의 어느 부분을 수정하려 할 때 관련된 작은 일부만을 이해해도 가능하게 해주는 능력이다.
    • 연관된 요소들을 함께 묶고, 요소 사이의 연결 관계를 쉽게 찾고 이해할 수 있도록 해야 한다.
  • 모든 함수는 컨텍스트안에 존재한다. 객체 지향 프로그래밍에서 핵심 모듈화 컨텍스트는 클래스다.
  • 어떤 함수가 자신이 속한 모듈의 요소보다 다른 모듈의 요소들을 더 많이 참조한다면 옮겨주는것이 마땅하다.
  • 함수 중 독립적으로도 고유한 가치가 있거나 다른 클래스로 옮겨두면 사용하기 더 편한 메소드도 옮기는게 낫다.
  • 함수를 옮길지 말기 고민된다면 대상 함수의 현재 컨텍스트와 후보 컨텍스트를 둘러보자
    • 대상 함수를 호출하는 함수
    • 대상 함수가 호출하는 함수
    • 대항 함수가 사용하는 데이터
  • 함수가 있어야 할 최적의 장소를 정하기 어려울수록 큰 문제가 아닌 경우가 많다.
  • 우선 한 컨텍스트에 모아두고 잘 맞지 않는다고 판단되면 위치를 옮기는 방법도 괜찮다.

절차

  1. 선택한 함수가 현재 컨텍스트에서 사용 중인 모든 프로그램 요소를 살피고, 요소들 중에 함께 옮겨야 할 게 있는지 본다.
    1. 호출되는 함수 중 함께 옮길 게 있다면 대체로 그 함수를 먼저 옮기는 게 낫다. 얽혀있는 함수가 여러 개라면 다른 곳에 미치는 영향이 적은 함수부터 옮긴다.
    2. 하위 함수들의 호출자가 고수준 함수 하나뿐이면 먼저 하위 함수들을 고수준 함수에 인라인하고, 고수준 함수들을 옮긴 뒤 옮긴 위치에서 개별 함수들로 추출한다.
  2. 선택한 함수가 다형 메서드인지 확인한다.
    1. 객체 지향 언어에서는 같은 메서드가 슈퍼클래스나 서브클래스에도 선언되어 있는지까지 고려해야 한다.
  3. 선택한 함수를 타깃 컨텍스트로 복사하고 다듬는다.
    1. 함수 본문에서 소스 컨텍스트의 요소를 사용한다면 해당 요소들을 매개변수로 넘기거나 소스 컨텍스트 자체를 참조로 넘겨준다.
    2. 함수를 옮기게 되면 새로운 컨텍스트에 어울리는 이름으로 바꿔줘야 할 경우가 있다.
  4. 정적 분석을 수행한다
  5. 소스 컨텍스트에서 타깃 함수를 참조할 방법을 찾아 반영한다.
  6. 소스 함수를 타깃 함수의 위임 함수가 되도록 수정한다.
  7. 테스트한다.
  8. 소스 함수를 인라인할지 고민해본다
    1. 소스 함수는 언제까지라도 위임 함수로 남겨둘 수 있다. 다만 소스 함수를 호출하는 곳에서 타깃 함수를 직접 호출하는 데 무기라 없다면 중간 단계 소스 함수는 제거하는 편이 낫다.

예시

public class Customer {
  private String name;
  private List<Rental> rentals;

  public Customer(String name) {
    this.name = name;
    this.rentals = new ArrayList<>();
  }

  public void addRental(Rental rental) {
    rentals.add(rental);
  }

  public String getName() {
    return name;
  }

  public String statement() {
    double totalAmount = 0;
    int frequentRenterPoints = 0;

    StringBuilder result = new StringBuilder("영화대여 내역 " + getName() + "\n");

    for (Rental rental : rentals) {
      double thisAmount = getCharge(rental);

      frequentRenterPoints++;

      if ((rental.getMovie().getPriceCode() == Movie.NEW_RELEASE) && (rental.getDaysRented() > 1)) {
        frequentRenterPoints++;
      }

      result.append("\t").append(rental.getMovie().getTitle()).append("\t").append(thisAmount).append("\n");
      totalAmount += thisAmount;
    }

    result.append("전체금액 :  ").append(totalAmount).append("\n");
    result.append("총 ").append(frequentRenterPoints).append(" 포인트를 얻었습니다.");

    return result.toString();
  }

  //요금을 계산하는 함수. Rental의 요소들을 더 많이 참조하고 있다.
  public double getCharge(Rental rental) {
    double result = 0;

    switch (rental.getMovie().getPriceCode()) {
      case Movie.REGULAR:
        result += 2;
        if (rental.getDaysRented() > 2) {
          result += (rental.getDaysRented() - 2) * 1.5;
        }
        break;

      case Movie.NEW_RELEASE:
        result += rental.getDaysRented() * 3;
        break;

      case Movie.CHILDREN:
        result += 1.5;
        if (rental.getDaysRented() > 3) {
          result += (rental.getDaysRented() - 3) * 1.5;
        }
        break;
    }

    return result;
  }

}

public class Rental {
    private Movie movie;
    private int daysRented;

    public Rental(Movie movie, int daysRented) {
      this.movie = movie;
      this.daysRented = daysRented;
    }

    public int getDaysRented() {
      return daysRented;
    }

    public Movie getMovie() {
      return movie;
    }

  }
public class Customer {
  private String name;
  private List<Rental> rentals;

  public Customer(String name) {
    this.name = name;
    this.rentals = new ArrayList<>();
  }

  public void addRental(Rental rental) {
    rentals.add(rental);
  }

  public String getName() {
    return name;
  }

  public String statement() {
    double totalAmount = 0;
    int frequentRenterPoints = 0;
    StringBuilder result = new StringBuilder("영화대여 내역 " + getName() + "\n");

    for (Rental rental : rentals) {
      double thisAmount = rental.getCharge();

      frequentRenterPoints++;
      if ((rental.getMovie().getPriceCode() == Movie.NEW_RELEASE) && (rental.getDaysRented() > 1)) {
        frequentRenterPoints++;
      }

      result.append("\t").append(rental.getMovie().getTitle()).append("\t").append(thisAmount).append("\n");
      totalAmount += thisAmount;
    }

    result.append("전체금액 :  ").append(totalAmount).append("\n");
    result.append("총 ").append(frequentRenterPoints).append(" 포인트를 얻었습니다.");

    return result.toString();
  }

}

public class Rental {
    private Movie movie;
    private int daysRented;

    public Rental(Movie movie, int daysRented) {
      this.movie = movie;
      this.daysRented = daysRented;
    }

    public int getDaysRented() {
      return daysRented;
    }

    public Movie getMovie() {
      return movie;
    }

        // 기존 Customer에 있던 함수를 Rental class로 이동
    public double getCharge() {
      double result = 0;

      switch (movie.getPriceCode()) {
        case Movie.REGULAR:
          result += 2;
          if (getDaysRented() > 2) {
            result += (getDaysRented() - 2) * 1.5;
          }
          break;

        case Movie.NEW_RELEASE:
          result += getDaysRented() * 3;
          break;

        case Movie.CHILDREN:
          result += 1.5;
          if (getDaysRented() > 3) {
            result += (getDaysRented() - 3) * 1.5;
          }
          break;
      }

      return result;
    }

  }

8.2 필드 옮기기

배경

  • 데이터 구조는 굉장히 중요하지만 제대로 하기가 어렵다
  • 그렇기 때문에 적절치 않은 데이터 구조는 최대한 빨리 바로잡아야 한다.
  • 필드 옮기기 리팩터링은 대체로 더 큰 변경의 일환으로 수행된다.

절차

  1. 소스 필드가 캡슐화되어 있지 않다면 캡슐화한다.
  2. 테스트한다.
  3. 타깃 객체에 필드와 접근자 메서드들을 생성한다.
  4. 정적 검사를 수행한다.
  5. 소스 객체에서 타깃 객체를 참조할 수 있는지 확인한다.
    1. 기존 필드나 메서드 중 타깃 객체를 넘겨주는 게 았을지 모른다. 없다면 이런 기능의 메서드를 쉽게 만들 수 있는지 살펴보고 간단하지 않다면 타깃 객체를 저장할 새 필드를 소스 객체에 생성하자. 이는 더 넓은 맥락에서 리펙터링을 충분히 하고 나면 다시 없앨 수 있을 때도 있다.
  6. 접근자들이 타깃 필드를 사용하도록 수정한다.
    1. 여러 소스에서 같은 타깃을 공유한다면, 먼저 세터를 수정해 타깃 필드와 소스 필드 모두를 갱신하게 하고, 이어서 일관성을 깨뜨리는 갱신을 검출할 수 있도록 어서션을 추가하자. 모두 잘 마무리 되었다면 접근자들이 타깃 필드를 사용하도록 수정한다.
  7. 테스트한다.
  8. 소스 필드를 제거한다.
  9. 테스트한다.

예시

8.3 문장을 함수로 옮기기

배경

  • 문장들을 함수로 옮기려면 그 문장들이 피호출 함수의 일부라는 확신이 있어야 한다.
  • 피호출 함수와 한 몸은 아니지만 여전히 함께 호출돼야 하는 경우라면 단순히 해당 문장들과 피호출 함수를 통째로 또 하나의 함수로 추출한다. 다만 마지막의 인라인과 이름 바꾸기 단계만 제외한다.

절차

  1. 반복 코드가 함수 호출 부분과 멀리 떨어져 있다면 문장 슬라이드하기를 적용해 근처로 옮긴다
  2. 타깃 함수를 호출하는 곳이 한 곳뿐이면, 단순히 소스 위치에서 해당 코드를 잘라내어 피호출 함수로 복사하고 테스트한다. 이 경우 나머지 단계는 무시한다.
  3. 호출자가 둘 이상이면 호출자 중 하나에서 ‘타깃 함수 호출 부분과 그 함수로 옮기려는 문장들을 함께’ 다른 함수로 추출한다. 추출한 함수에 기억하기 쉬운 임시 이름을 지어준다.
  4. 다른 호출자 모두가 방금 추출한 함수를 사용하도록 수정한다. 하나씩 수정할 때마다 테스트한다.
  5. 모든 호출자가 새로운 함수를 사용하게 되면 원래 함수를 새로운 함수 안으로 인라인한 후 원래 함수를 제거한다.
  6. 새로운 함수의 이름을 원래 함수의 이름으로 바꿔준다. 더 나은 이름이 있다면 그 이름을 쓴다.

예시

public String statement() {
    double totalAmount = 0;
    int frequentRenterPoints = 0;

    StringBuilder result = new StringBuilder("영화대여 내역 " + getName() + "\n");

    for (Rental rental : rentals) {
      double thisAmount = rental.getCharge();

      frequentRenterPoints++;

      if ((rental.getMovie().getPriceCode() == Movie.NEW_RELEASE) && (rental.getDaysRented() > 1)) {
        frequentRenterPoints++;
      }

      result.append("\t").append(rental.getMovie().getTitle()).append("\t").append(thisAmount).append("\n");
      totalAmount += thisAmount;
    }

    printRentalInformation(result, totalAmount);

    result.append("총 ").append(frequentRenterPoints).append(" 포인트를 얻었습니다.");

    return result.toString();
  }

  public StringBuilder printRentalInformation(StringBuilder result, double totalAmount) {
    result.append("전체금액 :  ").append(totalAmount).append("\n");

    return result;
  }
public String statement() {
    double totalAmount = 0;
    int frequentRenterPoints = 0;

    StringBuilder result = new StringBuilder("영화대여 내역 " + getName() + "\n");

    for (Rental rental : rentals) {
      double thisAmount = rental.getCharge();

      frequentRenterPoints++;

      if ((rental.getMovie().getPriceCode() == Movie.NEW_RELEASE) && (rental.getDaysRented() > 1)) {
        frequentRenterPoints++;
      }

      result.append("\t").append(rental.getMovie().getTitle()).append("\t").append(thisAmount).append("\n");
      totalAmount += thisAmount;
    }

    printRentalInformation(result, totalAmount, frequentRenterPoints);

    return result.toString();
  }

  public StringBuilder printRentalInformation(StringBuilder result, double totalAmount, int frequentRenterPoints) {
    result.append("전체금액 :  ").append(totalAmount).append("\n");
    result.append("총 ").append(frequentRenterPoints).append(" 포인트를 얻었습니다.");

    return result;
  }

8.4 문장을 호출한 곳으로 옮기기

배경

  • 초기에는 한 가지 일만 수행하던 함수가 어느새 둘 이상의 다른 일을 수행하게 되는 경우엔 우선 문장 슬라이드하기를 적용해 달라지는 함수의 시작 혹은 끝으로 옮긴 다음, 바로 이어서 문장을 호출한 곳으로 옮기기 리팩터링을 적용한다.
  • 변경이 작다면 문장을 호출한 곳으로 옮기는 것으로 충분하지만 호출자와 호출 대상의 경계를 완전히 다시 그어야 할 수도 있다.
    • 함수 인라인하기 부터 적용한 뒤, 문장 슬라이드하기 와 함수 추출하기로 적합한 경계를 설정한다.

절차

  1. 호출자가 한두 개뿐이고 피호출 함수도 간단한 단순한 상황이면, 피호출 함수의 처음 혹은 마지막줄들을 잘라내서 호출자들로 복사해 넣는다. 필요시 적당히 수정하고 테스트만 통과하면 이번 리팩터링은 여기서 끝이다.
  2. 더 복잡한 케이스에서는, 이동하지 ‘않길’원하는 모든 문장을 함수로 추출한 다음 검색하기 쉬운 임시 이름을 지어준다.
    1. 대상 함수가 서브클래스에서 오버라이드 됐다면 오버라이드한 서브클래스들의 메서드 모두에서 동일하게 남길 부분을 메서드로 추출한다..
    2. 이때 남겨질 메서드의 본문은 모든 클래스에서 똑같아야 한다.
    3. 그런 다음 슈퍼클래스의 메서드만 남기고 서브클래스들의 메서드를 제거한다.
  3. 원래 함수를 인라인한다.
  4. 추출된 함수의 이름을 원해 함수의 이름으로 변경한다.

예시

public String statement() {
    double totalAmount = 0;
    int frequentRenterPoints = 0;

    StringBuilder result = new StringBuilder("영화대여 내역 " + getName() + "\n");

    for (Rental rental : rentals) {
      double thisAmount = rental.getCharge();

      frequentRenterPoints++;

      if ((rental.getMovie().getPriceCode() == Movie.NEW_RELEASE) && (rental.getDaysRented() > 1)) {
        frequentRenterPoints++;
      }

      result.append("\t").append(rental.getMovie().getTitle()).append("\t").append(thisAmount).append("\n");
      totalAmount += thisAmount;
    }

    printRentalInformation(result, totalAmount, frequentRenterPoints);

    return result.toString();
  }

  public StringBuilder printRentalInformation(StringBuilder result, double totalAmount, int frequentRenterPoints) {
    result.append("전체금액 :  ").append(totalAmount).append("\n");
    result.append("총 ").append(frequentRenterPoints).append(" 포인트를 얻었습니다.");

    return result;
  }
public String statement() {
    double totalAmount = 0;
    int frequentRenterPoints = 0;

    StringBuilder result = new StringBuilder("영화대여 내역 " + getName() + "\n");

    for (Rental rental : rentals) {
      double thisAmount = rental.getCharge();

      frequentRenterPoints++;

      if ((rental.getMovie().getPriceCode() == Movie.NEW_RELEASE) && (rental.getDaysRented() > 1)) {
        frequentRenterPoints++;
      }

      result.append("\t").append(rental.getMovie().getTitle()).append("\t").append(thisAmount).append("\n");
      totalAmount += thisAmount;
    }

    printRentalInformation(result, totalAmount);
    result.append("총 ").append(frequentRenterPoints).append(" 포인트를 얻었습니다.");

    return result.toString();
  }

  public StringBuilder printRentalInformation(StringBuilder result, double totalAmount) {
    result.append("전체금액 :  ").append(totalAmount).append("\n");

    return result;
  }

8.5 인라인 코드를 함수 호출로 바꾸기

배경

  • 이미 존재하는 함수와 똑같은 일을 하는 인라인 코드를 발견하면 보통은 해당 코드를 함수 호출로 바꿀텐데, 순전히 우연히 비슷한 코드가 만들어졌을 때 처럼 기존 함수의 코드를 수정하더라도 인라인 코드의 동작은 바뀌지 않아야 할 때는 예외적으로 처리해야한다.
    • 이를 판단하기 위해서 인라인 코드 대신 함수 이름을 넣었을때 말이 되는지 확인한다.
    • 어색하다면 이름이 잘못 되었거나 해당 함수의 목적이 인라인 코드의 목적과 다르기 때문일 것이다.

절차

  1. 인라인 코드를 함수 호출로 대체한다.
  2. 테스트한다.

예시

public class StringOperations {
    public static void main(String[] args) {
        List<String> names = new ArrayList<>();
        names.add("김");
        names.add("박");
        names.add("이");
        names.add("조");

        boolean containsCharlie = false;
        for (String name : names) {
            if (name.equals("김")) {
                containsCharlie = true;
                break;
            }
        }

        int numberOfNames = names.size();
        System.out.println("'김'씨를 포함 여부: " + containsCharlie);
        System.out.println("성씨 수: " + numberOfNames);
    }
}
public class StringOperations {
    public static void main(String[] args) {
        List<String> names = new ArrayList<>();
        names.add("김");
        names.add("박");
        names.add("이");
        names.add("조");

        boolean containsCharlie = names.contains("김");
        int numberOfNames = names.size();

        System.out.println("'김'씨를 포함 여부: " + containsCharlie);
        System.out.println("성씨 수: " + numberOfNames);
    }
}

8.6 문장 슬라이드하기

배경

  • 관련된 코드들은 서로 가까이 모여 있어야 이해하기 쉽다.
  • 관련 코드끼리 모으는 작업은 다른 리팩터링, 주로 함수 추출하기의 준비 단계로 자주 행해진다.

절차

  1. 코드 조각을 이동할 목표 위치를 찾는다. 코드 조각의 원래 위치와 목표 위치 사이의 코드들을 훑어보면서, 조각을 모으고 나면 동작이 달라지는 코드가 있는지 살핀다.
    1. 아래와 같은 간섭이 있다면 이 리팩터링은 포기해야한다.
      1. 코드 조각에서 참조하는 요소를 선언하는 문장 앞으로는 이동할 수 없다.
      2. 코드 조각을 참조하는 요소의 뒤로는 이동할 수 없다.
      3. 코드 조각에서 참조하는 요소를 수정하는 문장을 건너뛰어 이동할 수 없다.
      4. 코드 조각이 수정하는 요소를 참조하는 요소를 건너뛰어 이동할 수 없다.
  2. 코드 조각을 원래 위치에서 잘라내어 목표 위치에 붙여 넣는다.
  3. 테스트한다.

테스트 실패시 더 작게 나눠 시도해본다. 이동 거리를 줄이거나 한 번에 옮기는 조각의 크기를 줄이는 방법이 있다.

예시

List<Movie> rentedMovies = getRentedMovie();
int totalAmount = getTotalAmount();

List<String> redtedMovieTitles = rentedMovies.stream().map(e -> e.getTitle()).collect(Collections.toList());
int amountToPay = totalAmount - 20;
List<Movie> rentedMovies = getRentedMovie();
List<String> redtedMovieTitles = rentedMovies.stream().map(e -> e.getTitle()).collect(Collections.toList());

int totalAmount = getTotalAmount();
int amountToPay = totalAmount - 20;

8.7 반복문 쪼개기

배경

  • 하나의 반복문에서 두 가지 일을 수행하는 경우가 있다. 이를 분리하여 각각의 반복문으로 분리한다.
  • 리팩터링을 실행한 후 병목이라 밝혀지만 그때 다시 하나로 합치면 된다.

절차

  1. 반복문을 복제해 두 개로 만든다
  2. 반복문이 중복되어 생기는 부수효과를 파악해서 제거한다.
  3. 테스트한다.
  4. 완료됐으면, 각 반복문을 함수로 추출할지 고민한다.

예시

public void processTasks(List<String> items) {
    for (String item : items)
        System.out.println("Item: " + item);

        if (item.contains("apple")) {
            System.out.println("Found an apple!");
        } else {
            System.out.println("No apples here!");
        }

        int length = item.length();
        System.out.println("Item length: " + length);

        String upperCaseItem = item.toUpperCase();
        System.out.println("Uppercase Item: " + upperCaseItem);

        String processedItem = item + " - processed";
        System.out.println("Processed Item: " + processedItem);

        System.out.println("-------------------------");
    }
}
public void processTasks(List<String> items) {
    for (String item : items) {
        System.out.println("Item: " + item);
    }

    for (String item : items) {
        if (item.contains("apple")) {
            System.out.println("Found an apple!");
        } else {
            System.out.println("No apples here!");
        }
    }

    for (String item : items) {
        int length = item.length();
        System.out.println("Item length: " + length);
    }

    for (String item : items) {
        String upperCaseItem = item.toUpperCase();
        System.out.println("Uppercase Item: " + upperCaseItem);
    }

    for (String item : items) {
        String processedItem = item + " - processed";
        System.out.println("Processed Item: " + processedItem);
    }

    System.out.println("-------------------------");
}

8.8 반복문을 파이프라인으로 바꾸기

배경

  • 파이프라인으로 표현하면 이해하기 훨씬 쉬워진다.

절차

  1. 반복문에서 사용하는 컬렉션을 가리키는 변수를 하나 만든다.
    1. 기존 변수를 단순히 복사한 것일 수도 있다.
  2. 반복문의 첫 줄부터 시작해서, 각각의 단위 행위를 적절한 컬렉션 파이프라인 연산으로 대체한다.
    1. 이때 컬렉션 파이프라인 연산은 1번에서 만든 반복문 컬렉션 변수에서 시작하여, 이전 연산의 결과를 기초로 연쇄적으로 수행된다. 하나를 대체할때마다 테스트한다.
  3. 반복문의 모든 동작을 대체했다면 반복문 자체를 지운다.
    1. 반복문이 결과를 누적 변수에 대입했다면 파이프라인의 결과를 그 누적 변수에 대입한다.

예시

public List<Movie> getMoviesFromRentals(List<Rental> rentals) {
    List<Movie> movies = new ArrayList<>();

    for (Rental rental : rentals) {
        Movie movie = rental.getMovie();
        movies.add(movie);
    }

    return movies;
}
public List<Movie> getMoviesFromRentals(List<Rental> rentals) {
    return rentals.stream()
            .map(Rental::getMovie)
            .collect(Collectors.toList());
}

8.9 죽은 코드 제거하기

배경

  • 사용되지 않는 코드는 소프트웨어의 동작을 이해하는 데 커다란 걸림돌이 된다.

절차

  1. 죽은 코드를 외부에서 참조할 수 있는 경우라면 혹시라도 호출하는 곳이 있는지 확인한다.
  2. 없다면 죽은 코드를 제거한다.
  3. 테스트한다.
public class Customer {
  private String name;
  private List<Rental> rentals;

  public Customer(String name) {
    this.name = name;
    this.rentals = new ArrayList<>();
  }

  public void addRental(Rental rental) {
    rentals.add(rental);
  }

  public String getName() {
    return name;
  }

  public String statement() {
    double totalAmount = 0;
    int frequentRenterPoints = 0;
    StringBuilder result = new StringBuilder("영화대여 내역 " + getName() + "\n");

    for (Rental rental : rentals) {
      double thisAmount = rental.getCharge();

      frequentRenterPoints++;
      if ((rental.getMovie().getPriceCode() == Movie.NEW_RELEASE) && (rental.getDaysRented() > 1)) {
        frequentRenterPoints++;
      }

      result.append("\t").append(rental.getMovie().getTitle()).append("\t").append(thisAmount).append("\n");
      totalAmount += thisAmount;
    }

    result.append("전체금액 :  ").append(totalAmount).append("\n");
    result.append("총 ").append(frequentRenterPoints).append(" 포인트를 얻었습니다.");

    return result.toString();
  }

//  public double getCharge(Rental rental) {
//    double result = 0;
//
//    switch (rental.getMovie().getPriceCode()) {
//      case Movie.REGULAR:
//        result += 2;
//        if (rental.getDaysRented() > 2) {
//          result += (rental.getDaysRented() - 2) * 1.5;
//        }
//        break;
//
//      case Movie.NEW_RELEASE:
//        result += rental.getDaysRented() * 3;
//        break;
//
//      case Movie.CHILDREN:
//        result += 1.5;
//        if (rental.getDaysRented() > 3) {
//          result += (rental.getDaysRented() - 3) * 1.5;
//        }
//        break;
//    }
//
//    return result;
//  }

}
public class Customer {
  private String name;
  private List<Rental> rentals;

  public Customer(String name) {
    this.name = name;
    this.rentals = new ArrayList<>();
  }

  public void addRental(Rental rental) {
    rentals.add(rental);
  }

  public String getName() {
    return name;
  }

  public String statement() {
    double totalAmount = 0;
    int frequentRenterPoints = 0;
    StringBuilder result = new StringBuilder("영화대여 내역 " + getName() + "\n");

    for (Rental rental : rentals) {
      double thisAmount = rental.getCharge();

      frequentRenterPoints++;
      if ((rental.getMovie().getPriceCode() == Movie.NEW_RELEASE) && (rental.getDaysRented() > 1)) {
        frequentRenterPoints++;
      }

      result.append("\t").append(rental.getMovie().getTitle()).append("\t").append(thisAmount).append("\n");
      totalAmount += thisAmount;
    }

    result.append("전체금액 :  ").append(totalAmount).append("\n");
    result.append("총 ").append(frequentRenterPoints).append(" 포인트를 얻었습니다.");

    return result.toString();
  }

}
728x90

'Study > Refactoring' 카테고리의 다른 글

리팩터링 11  (0) 2023.08.29
리팩터링 챕터 10  (0) 2023.08.28
리팩터링 챕터 9  (0) 2023.08.27
리팩터링 챕터7  (0) 2023.07.17
리팩터링 챕터 6  (0) 2023.07.16