원시값 포장
원시값 포장하기
자동차 경주 게임의 요구사항 중, 자동차의 이름은 5글자를 초과할 수 없다.
라는 규칙이 있다.
또한 자동차의 이름은 비어있는 값이 될 수 없다. 최소 1글자는 되어야 한다.
사용자가 입력하는 이름에 따라, Car 객체 생성 전에 이름에 대한 검증이 필요하다.
public class Car {
private static final int MAX_NAME_LENGTH = 5;
private static final String NAME_LENGTH_ERROR = "[ERROR] 이름은 5글자를 초과할 수 없습니다.";
private static final String EMPTY_NAME_ERROR = "[ERROR] 이름은 공백일 수 없습니다.";
private final String name;
private int position;
public Car(String name) {
validateEmptyName(name);
validateName(name);
this.name = name;
position = 0;
}
private void validateEmptyName(String name) {
if (name.isEmpty()) {
throw new IllegalArgumentException(EMPTY_NAME_ERROR);
}
}
private void validateName(String name) {
if (name.length() > MAX_NAME_LENGTH) {
throw new IllegalArgumentException(NAME_LENGTH_ERROR);
}
}
}
현재는 이름에 대한 검증만 있기 때문에, Car 클래스에서도 충분히 할 수 있다고 생각을 할 수 있겠지만, 프로그램이 커져 요구사항이 많아 진다면 한 객체가 너무 많은 일을 담당하고 있을 수 있다.
원시 타입의 변수를 포장해보겠다.
public class Name {
private static final int MAX_NAME_LENGTH = 5;
private static final String NAME_LENGTH_ERROR = "[ERROR] 이름은 5글자를 초과할 수 없습니다.";
private static final String EMPTY_NAME_ERROR = "[ERROR] 이름은 공백일 수 없습니다.";
private final String name;
public Name(String name) {
validateEmptyName(name);
validateName(name);
this.name = name;
}
private void validateEmptyName(String name) {
if (name.isEmpty()) {
throw new IllegalArgumentException(EMPTY_NAME_ERROR);
}
}
private void validateName(String name) {
if (name.length() > MAX_NAME_LENGTH) {
throw new IllegalArgumentException(NAME_LENGTH_ERROR);
}
}
public String getName() {
return name;
}
}
public class Car {
private static final int MOVE_CRITERIA = 4;
private static final int POSITION_INITIALIZATION = 0;
private final Name name;
private final Position position;
public Car(String name) {
this.name = new Name(name);
this.position = new Position(POSITION_INITIALIZATION);
}
}
name에 대한 유효성 검증이나 상태 관리를 Name 객체에서 스스로 하게 되면서, Car와 Name의 책임이 명확해졌다.
원시값 포장은 유지 보수에 도움을 준다.
우리가 프리코스로 진행했던 자판기 미션의 Money 객체로 포장을 하면서 얻을 수 있는 이득이다.
public class Money {
private static final String INVALID_MONEY_ERROR_MESSAGE = "[ERROR] 금액은 0원 이상 입력해야 한다.";
private static final String MERCHANDISE_PRICE_NOT_DIVIDE_10_COIN_ERROR_MESSAGE = "[ERROR] 입력하는 금액은 10원으로 나누어떨어져야 한다.";
private int money;
public Money(int money) {
validateMoney(money);
validateDivideMoneyBy10Coin(money);
this.money = money;
}
public static void validateMoney(int money) {
if (money <= 0) {
throw new IllegalArgumentException(INVALID_MONEY_ERROR_MESSAGE);
}
}
public static void validateDivideMoneyBy10Coin(int merchandisePrice) {
if (merchandisePrice % 10 != 0) {
throw new IllegalArgumentException(MERCHANDISE_PRICE_NOT_DIVIDE_10_COIN_ERROR_MESSAGE);
}
}
}
public class User {
private final Money userMoney;
private final List<Merchandise> buyingMerchandiseList;
public User(int userMoney) {
this.userMoney = new Money(userMoney);
buyingMerchandiseList = new ArrayList<>();
}
public int getUserMoney() {
return userMoney.getMoney();
}
public void buyMerchandise(Merchandise buyingMerchandise) {
buyingMerchandiseList.add(buyingMerchandise);
userMoney.deductMoney(buyingMerchandise.getMerchandiseMoney());
}
}
public class Merchandise {
private static final String INVALID_MERCHANDISE_INFORMATION_ERROR_MESSAGE = "[ERROR] 상품 정보는 상품명, 가격, 수량 순으로 모두 입력해야한다.";
private final Name name;
private final Money money;
private final Quantity quantity;
public Merchandise(String name, int money, int quantity) {
this.name = new Name(name);
this.money = new Money(money);
this.quantity = new Quantity(quantity);
}
}
만약, Money로 원시값 포장하지 않고 User 클래스에서, Merchandise 클래스에서 int money
로 바로 사용을 했다면, 음수에 대한 검증과 10원으로 나누어 떨어지는 값에 대한 검증을 두 클래스에서 모두 해주었을 것이다.
중복되는 코드가 많아 지게 된다.
또한 금액에 대한 요구사항이 바뀐다면, 금액이라는 상태를 가지고 있는 모든 클래스에 들어가서 바꿔주어야 할 것이다.
하지만, Money로 원시값 포장을 한 상황이라면, Money 만 수정해 주면 될 것이다.
참고
https://tecoble.techcourse.co.kr/post/2020-05-29-wrap-primitive-type/
https://brazen-serpent-b4f.notion.site/3-c34c9d0adc0b45499d844239ca2448e3