객체 지향 설계 연습하기 - 블랙잭 (1)
github source code
0. 들어가며
- 업무에 Java를 사용하고 있지만, 깊은 이해도가 부족하다는걸 절감.
- 단순 객체 생성 및 비즈니스 로직 구현에만 매달리고 있음. 회의감이 듦.
- 신규 개발 뿐만 아니라 유지 보수 및 리팩토링시 객체 지향의 묘미를 살려보고자 함
- 객체 지향적 시야와 사고는 연습뿐이라는 것을 여러 커뮤니티에서 수집
- 객체 지향 설계 연습을 통해 객체 지향적 시야와 이해력을 높이고지 함
1. 리팩토링
- Card
- Suit와 Denomination의 구현체로 변경
- 추상화 개념에서 한개의 카드는 한 개의 무늬와 끗수를 가짐
- 체크 메소드를 통해 카드를 확인하는 행동을 구현
package personal.cjh.BlackJack.card;
import org.apache.commons.lang3.tuple.ImmutablePair;
public class Card {
private Suit suit;
private Denomination denomination;
public Card(Suit suit, Denomination denomination) {
this.suit = suit;
this.denomination = denomination;
}
public ImmutablePair<Suit, Denomination> check() {
return new ImmutablePair<Suit, Denomination>(suit, denomination);
}
}
package personal.cjh.BlackJack.card;
import org.apache.commons.lang3.tuple.ImmutablePair;
public class Card {
private Suit suit;
private Denomination denomination;
public Card(Suit suit, Denomination denomination) {
this.suit = suit;
this.denomination = denomination;
}
public ImmutablePair<Suit, Denomination> check() {
return new ImmutablePair<Suit, Denomination>(suit, denomination);
}
}
package personal.cjh.BlackJack.card;
public enum Suit {
SPADE("SPADE"), DIAMOND("DIAMOND"), CLOVER("CLOVER"), HEART("HEART");
private String Property;
Suit(String property) {
this.Property = property;
}
public String getProperty() {
return Property;
}
}
- Deck
- 덱은 Card의 변경에따라 다소 변경이 있음
- Card 객체를 직접 사용함에 따라 코드가 직관적으로 바뀜
- DrawCard 메소드는 카드 객체를 반환하고, Card를 받은 유저가 check 메소드 호출을 통해 끗수와 무늬를 확인
package personal.cjh.BlackJack.deck;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import personal.cjh.BlackJack.card.Card;
import personal.cjh.BlackJack.card.Denomination;
import personal.cjh.BlackJack.card.Suit;
public class Deck {
private final List<Card> deck = new ArrayList<>();
private final Random random = new Random();
public Deck() {
for (int i = 0; i < Suit.values().length; i++) {
for (int j = 0; j < Denomination.values().length; j++) {
deck.add(new Card(Suit.values()[i], Denomination.values()[j]));
}
}
}
public Card drawCard() {
int index = random.nextInt(deck.size());
Card card = deck.get(index);
deck.remove(index);
return card;
}
public List<Card> getDeck() {
return deck;
}
}
테스트
package personal.cjh.BlackJack.deck;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.*;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import org.junit.Test;
import personal.cjh.BlackJack.card.Card;
import personal.cjh.BlackJack.card.Denomination;
import personal.cjh.BlackJack.card.Suit;
public class DeckTest {
@Test
public void 인스턴스로_생성된_덱의_유효성_검사() {
Deck deck = new Deck();
boolean nullPointCheck = deck == null;
boolean instanceCheck = deck instanceof Deck;
boolean deckSizeCheck = deck.getDeck().size() == deck.getDeck().size();
deck.drawCard();
assertFalse(nullPointCheck);
assertTrue(instanceCheck);
assertTrue(deckSizeCheck);
}
@Test
public void 덱에서_카드를_뽑았을때_덱의_상태변화를_검사한다() {
Deck deck = new Deck();
int beforeDeckSize = deck.getDeck().size();
Card card = deck.drawCard();
int afterDeckSize = deck.getDeck().size();
assertFalse(beforeDeckSize == afterDeckSize);
assertTrue(beforeDeckSize == 52);
assertTrue(afterDeckSize == 51);
}
@Test
public void 덱에서_카드를_뽑았을때_카드의_유효성을_검사한다() {
Deck deck = new Deck();
Card card = deck.drawCard();
assertTrue(card != null);
assertThat(card, instanceOf(Card.class));
assertThat(card.check(), instanceOf(ImmutablePair.class));
assertThat(card.check().left, instanceOf(Suit.class));
assertThat(card.check().right, instanceOf(Denomination.class));
}
@Test
public void 덱이_싱글톤인지_검사한다() {
Deck deck = new Deck();
Deck deck2 = new Deck();
boolean beforeDrawCheck = (deck.getDeck() == deck.getDeck());
deck.drawCard();
boolean afterDrawCheck = (deck.getDeck() == deck.getDeck());
boolean isEqual = (deck.getDeck() == deck2.getDeck());
assertTrue(beforeDrawCheck);
assertTrue(afterDrawCheck);
assertFalse(isEqual);
}
@Test
public void getCard_함수를_사용했을때_덱안의_카드가_정상적으로_줄어드는지_검사() {
Deck deck = new Deck();
boolean initSizeEqual = (deck.getDeck().size() == 52);
Card card = deck.drawCard();
assertThat(card.check().getLeft(), instanceOf(Suit.class));
assertThat(card.check().getRight(), instanceOf(Denomination.class));
assertThat(deck.getDeck().size(), is(51));
int count = 51;
while (deck.getDeck().size() != 0) {
Card cd = deck.drawCard();
printCard(cd.check(), count);
assertThat(deck.getDeck().size(), is(--count));
}
}
public void printCard(Pair<Suit, Denomination> card, int count) {
StringBuilder sb = new StringBuilder();
sb.append("COUNT : ");
sb.append(count);
sb.append(" Suit : ");
sb.append(card.getLeft());
sb.append(" Suit Prop : ");
sb.append(card.getLeft().getProperty());
sb.append(" Denomination : ");
sb.append(card.getRight());
sb.append(" Denomination Prop : ");
sb.append(card.getRight().getProperty());
System.out.println(sb.toString());
sb.setLength(0);
}
}
마무리
- 카드쪽에서 고민이 많았는데 abstract, interface 대신 Suit와 Denomination을 구현한 클래스를 사용함으로써 전체적인 코드가 직관적으로 바뀜