우아한 테크 코스 2주차 사다리 타기

Jae-Baek Song·2023년 2월 18일
0
post-thumbnail

객체의 값을 외부에 노출할 때 또한 방어적 복사를 통해 보호해야 한다.

이것은 Joshua Bloch 의 책 "Effective Java" 에서 논의됩니다 .
"필요할 때 방어적인 복사본 만들기" 라는 섹션이 있습니다 (2판의 섹션 39).

List<String> dislikeMenus() {
  return new ArrayList<>(dislikeMenus);
}

final var crew = new Crew("나시고렝", "파인애플 볶음밥", "미소시루", "하이라이스");
final List<String> dislikeMenus = crew.dislikeMenus(); // ["나시고렝", "파인애플 볶음밥", "미소시루", "하이라이스"]
dislikeMenus.clear(); // []
crew.dislikeMenus(); // ["나시고렝", "파인애플 볶음밥", "미소시루", "하이라이스"]
List<String> dislikeMenus() {
  return Collections.unmodifiableList(dislikeMenus);
}

final var crew = new Crew("나시고렝", "파인애플 볶음밥", "미소시루", "하이라이스");
final List<String> dislikeMenus = crew.dislikeMenus();
dislikeMenus.clear(); // 예외 발생

Getter에서 참조 자료형을 그대로 반환하는 경우
원본 객체가 수정될 수 있다.

List<String> dislikeMenus() {
  return dislikeMenus;
}

final var crew = new Crew("나시고렝", "파인애플 볶음밥", "미소시루", "하이라이스");
final List<String> dislikeMenus = crew.dislikeMenus(); // ["나시고렝", "파인애플 볶음밥", "미소시루", "하이라이스"]
dislikeMenus.clear();
crew.dislikeMenus(); // []

결론

  • 원본 객체에 영향이 간다면 복사를 통해 원본을 보호해야한다.

검증은 어디에서 해야할까?

검증 코드는 보통 Model 혹은 View 에서 처리한다.
검증을 외부에서 처리하고 Model에서 처리하지 않을 경우
Model 객체에 변경이 생길 경우 해당 객체가 유효한지 판단할 수 없다.

Model에서 검증을 한다면 View에서 검증 코드가 필요할까?

class Lotto {
  List<String> numbers
  String bonus;
  ...
}

로또 번호를 입력하세요: a, b, c, 가
보너스 번호를 입력하세요: 7
[ERROR] 로또 번호는 숫자여야 합니다

View에 검증 코드가 없다면 위와 같이 로또 번호 입력에서 잘못된 값이 입력되었지만 보너스 번호까지 입력 후에 에러가 발생한다.

하지만 위와 같은 문제도 Bonus 객체를 따로 생성할경우 해결할수 있다.

결론

  • Model 검증을 필수적이지만 View 검증은 필수적이지 않다.

일급 컬렉션

객체지향 생활체조 파트 규칙 8
"Collection을 포함한 클래스는 반드시 다른 멤버 변수가 없어야한다."

  • 비즈니스에 종속적인 자료구조

    일급 컬렉션을 사용하지 않는 경우 외부 클래스에 별도로 검증 로직이 추가된다.
    다른 역할을 하고 있는 외부 클래스가 또 다른 역할을 지니게 된다.

  • 상태와 행위를 한 곳에서 관리

    일급 컬렉션은 값과 로직이 함께 존재하기 때문에
    응집도가 높아진다.

  • Collection의 불변성을 보장

    Setter는 생성자를 통해 방어하고, Getter 또한 상태 데이터인 경우 갱체에 메시지를 보내는 방식으로 데이터의 외부 노출을 막을수있다.

    https://pomo0703.tistory.com/13


동작 파라미터화

사다리 게임 요구사항

함수(또는 메서드)의 길이가 10라인을 넘어가지 않도록 구현한다.
함수(또는 메서드)가 한 가지 일만 하도록 최대한 작게 만들어라.

요구 사항은 아니었지만 입력 값이 잘못 들어왔을 경우 재 입력을 받을 수 있게 구현하고 싶었다.

private Users retryOnUsersError() {
      try {
          return new Users(inputView.inputUserName())); //바뀌는 부분
      } catch (Exception e) {
          outputView.printExceptionMessage(e.getMessage());
          return retryOnUsersError();
      }
  }
private Height retryOnHeightError() {
      try {
          return new Height(inputView.inputLadderHeight()); //바뀌는 부분
      } catch (Exception e) {
          outputView.printExceptionMessage(e.getMessage());
          return retryOnHeightError();
      }
  }

위와 같이 try-catch문이 들어갈경우 default로 5라인을 사용하게된다.

소프트웨어 개발 3대 원칙
"똑같은 일을 두번하지 않는다. 중복되는 함수나 코드는 하나의 공통의 콤포넌트에 넣고 사용한다. 큰 시스템을 여러 조각으로 나누고 서로 참조한다."

동작 파라 미터화를 하면 문제를 해결할 수 있다.

private <T> T retryOnError(Supplier<T> runnable) {
      try {
          return runnable.get();
      } catch (Exception e) {
          outputView.printExceptionMessage(e.getMessage());
          return retryOnError(runnable);
      }
  }

Users users = retryOnError(() -> new Users(inputView.inputUserName()));
Height height = retryOnError(() -> new Height(inputView.inputHeight()));

정적 팩토리 메소드

객체 생성을 생성자가 아닌 정적(static) 메서드로 하는 것을 정적 팩토리 메서드라고 합니다.

Joshua Bloch 의 책 "Effective Java" 에서
"생성자 대신 정적 팩토리 메서드를 고려하십시오"

장점

  • 정적 팩터리 메서드의 장점 중 하나는 생성자와 달리 이름이 있다는 것입니다.
  • 정적 팩터리 메서드의 두 번째 장점은 생성자와 달리 호출될 때마다 새 개체를 만들 필요가 없다는 것입니다.
  • 정적 팩터리 메서드의 세 번째 장점은 생성자와 달리 반환 형식(인터페이스)의 모든 하위 형식 개체를 반환할 수 있다는 것입니다.
    • 입력 매개변수에 따라 매번 다른 클래스의 객체를 반환할 수 있다.

단점

  • 상속을 하려면 public이나 protected 생성자가 필요하니 정적 팩토리 메서드만 제공하면 하위 클래스 생성 불가

전략 패턴

객체가 할 수 있는 행위들 각각을 전략으로 만들어 놓고, 동적으로 행위의 수정이 필요한 경우 전략을 바꾸는 것만으로 행위의 수정이 가능하도록 만든 패턴입니다.

public interface BooleanGenerator {
    boolean get();
}

사다리 랜덤 생성

public class RandomBooleanGenerator implements BooleanGenerator {
    @Override
    public boolean get() {
        return new Random().nextBoolean();
    }
}

테스트 사다리 생성

public class TestRandomGenerator implements BooleanGenerator {
    private final Queue<Boolean> randomNumberQueue;

    public TestRandomGenerator(Queue<Boolean> randomNumberQueue) {
        this.randomNumberQueue = randomNumberQueue;
    }

    @Override
    public boolean get() {
        return randomNumberQueue.poll();
    }
}

사다리를 생성하는 행위에 대한 전략(랜덤 혹은 큐)클래스를 생성하고 전략을 바꾸는 것만으로 행위의 수정이 가능

0개의 댓글

관련 채용 정보