Replies: 4 comments
-
이 경우, 요구사항이 추가되어 도메인 로직이 추가로 존재할 경우에, 기능 중심으로 객체를 생성하는 것이 좋다고 생각합니다. |
Beta Was this translation helpful? Give feedback.
-
유의미한 기능을 포함하는지의 여부가 중요할 것 같아요. 내부에 중요한 로직을 포함하고 있다는 것은 객체에게 로직을 처리해야할 책임이 있다고 생각되며 결국 그것이 해당 객체의 기능이 되겠네요! 예시 코드의 경우 단순히 가지고 있는 |
Beta Was this translation helpful? Give feedback.
-
이는 C언어의 구조체(structure) 와 다르지 않다고 보입니다. 또한 저희 스터디 교재에서도 객체란 데이터 중심이 아닌 |
Beta Was this translation helpful? Give feedback.
-
객체의 역할이 없고 상태만 존재한다면, 앞선 설계에서 객체의 역할 분리가 잘 됐는지 확인하고 객체의 역할 재분리를 해야한다고 생각합니다. 로또 1단계에서 비슷한 실수를 했는데 예시로 보여드리겠습니다. public class LottoTicket {
private final List<Lotto> lottoTicket;
public LottoTicket(List<Lotto> lottoTicket) {
this.lottoTicket = new ArrayList<>(lottoTicket);
}
public List<Lotto> getLottoTicket() {
return Collections.unmodifiableList(lottoTicket);
}
} 위의 LottoTicket 클래스에서는 구매한 로또 리스트의
코멘트를 보고 생각해보니 LottoTicket 클래스의 역할은 없었습니다. 왜? 설계 시, "당첨 결과(WinningResult) 를 생성하는 역할을 어떤 객체가 처리할 것인가" 고민을 했고 두 가지 방법을 생각했습니다.
로또 티켓이 직접 당첨번호를 확인한다?는 모습이 어색했고, 구매한 로또와 당첨 번호를 한 번에 모두 전달해서 당첨 결과를 만들어내는 모습이 더 자연스럽다고 생각했습니다. 그래서 1번째 방법을 선택했었는데, 결론적으로 해당 방법은 역할 분리를 통한 설계가 아니라 철저하게 문제점
public class WinningResult {
public static WinningResult createWinningResult(LottoTicket lottoTicket, WinningNumbers winningNumbers) {
EnumMap<LottoRank, Integer> winningResult = initializeWinningResult();
Lotto winningNumber = winningNumbers.getWinningNumbers();
Number bonusNumber = winningNumbers.getBonusNumber();
addWinningResult(lottoTicket, winningResult, winningNumber, bonusNumber);
return new WinningResult(winningResult);
}
private static void addWinningResult(LottoTicket lottoTicket, EnumMap<LottoRank, Integer> winningResult, Lotto winningNumber, Number bonusNumber) {
// getter로 로또 정보를 가져와서 사용 함.
for (Lotto lotto : lottoTicket.getLottoTicket()) {
LottoRank rank = lotto.checkWinningResult(winningNumber, bonusNumber);
addWinningResultCount(winningResult, rank);
}
}
// ... 하위에 수익률 계산 메서드 존재
} 해결 방법데이터가 아니라
public class LottoTicket {
private static final int DEFAULT_RANK_COUNT = 0;
private final List<Lotto> lottoTicket;
public LottoTicket(List<Lotto> lottoTicket) {
this.lottoTicket = new ArrayList<>(lottoTicket);
}
public EnumMap<LottoRank, Integer> checkLottoTicketWinningCountByRank(WinningNumbers winningNumbers) {
EnumMap<LottoRank, Integer> winningCountByRank = initializeWinningCount();
Lotto winningNumber = winningNumbers.getWinningNumbers();
Number bonusNumber = winningNumbers.getBonusNumber();
for (Lotto lotto : lottoTicket) {
LottoRank rank = lotto.checkWinningResult(winningNumber, bonusNumber);
addWinningResultCount(winningCountByRank, rank);
}
return winningCountByRank;
}
}
// test
public class LottoTicketTest {
@Test
@DisplayName("1등 당첨 결과 확인")
void checkFirstWinningResult() {
// given
Lotto myLotto = new Lotto(Stream.of(1, 5, 9, 11, 16, 35)
.map(Number::new)
.collect(Collectors.toList()));
Lotto winningLotto = new Lotto(Stream.of(1, 5, 9, 11, 16, 35)
.map(Number::new)
.collect(Collectors.toList()));
Number bonusNumber = new Number(36);
WinningNumbers winningNumbers = new WinningNumbers(winningLotto, bonusNumber);
LottoTicket lottoTicket = new LottoTicket(List.of(myLotto));
// when
WinningResult winningResult = new WinningResult(lottoTicket.checkLottoTicketWinningCountByRank(winningNumbers));
// then
assertThat(winningResult.getWinningResult().get(LottoRank.FIRST)).isEqualTo(1);
} public class WinningResult {
private final Map<LottoRank, Integer> winningResult;
public WinningResult(EnumMap<LottoRank, Integer> winningResult) {
this.winningResult = new EnumMap<>(winningResult);
}
public double getRateOfProfit(Money money) {
long profit = winningResult.entrySet()
.stream()
.mapToLong(this::calculateProfit)
.sum();
return (double) profit / (double) money.getMoney();
}
}
// test
class WinningResultTest {
@Test
@DisplayName("1000원으로 1등 당첨 시, 수익률 확인")
void checkRateOfProfitFirstWinning() {
Lotto myLotto = new Lotto(Stream.of(1, 5, 9, 11, 16, 35)
.map(Number::new)
.collect(Collectors.toList()));
Lotto winningLotto = new Lotto(Stream.of(1, 5, 9, 11, 16, 35)
.map(Number::new)
.collect(Collectors.toList()));
Number bonusNumber = new Number(39);
WinningNumbers winningNumbers = new WinningNumbers(winningLotto, bonusNumber);
LottoTicket lottoTicket = new LottoTicket(List.of(myLotto));
Money money = new Money(1000);
// when
WinningResult winningResult = new WinningResult(lottoTicket.checkLottoTicketWinningCountByRank(winningNumbers));
//then
assertThat(winningResult.getRateOfProfit(money)).isEqualTo(2000000.0);
}
} 이러한 과정을 통해서 각자의 역할이 확실하게 분리되었고, 테스트 코드 역시 하나의 역할만을 테스트할 수 있었습니다. 또한 불필요한 getter 사용없이, 객체에게 메시지를 던지는 방법으로 로직을 처리할 수 있었습니다. 정리객체는 역할을 중심으로 설계해야 한다. 만약 객체의 상태만 존재하거나 하나의 객체에서 너무 많은 역할을 수행하고 있다면, '데이터 중심으로 설계한 것은 아닌가?', '역할 분리를 적절하게 했나?' 고민해보고 객체의 역할을 재분리하는 과정을 수행하는 것이 좋다. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
👋 매트, 케이 오늘 발표 잘 들었습니다!
❓ 책을 읽어보면 메시지를 통해 그 메세지를 수행할 적절한 책임을 수행함으로써 객체가 생성된다고 해요. 결국 객체는 기능을 통해서 생성이 되고, 가지고 있는 상태로는 만들어지지 않는데요. 오늘 예시 코드에 나왔던 클래스를 객체로 생각할 수 있을까요?
Beta Was this translation helpful? Give feedback.
All reactions