우아한 테크 캠프 Pro 프리코스 진행 중에, 원시 값 포장이라는 개념을 접하게 되었다
원시 값 포장을 쉽게 설명하자면 int, long과 같은 Primitave 타입 변수를 특정 객체로 감싸는 행위를 말한다
코드로 살펴보자면
public class Wallet {
private long money;
}
위 지갑 클래스의 돈 이라는 long 형 변수를 포장하는 것이다. 바로 이렇게
public class Wallet {
private Money money;
}
public class Money {
private long money;
}
질문이 있어요.
Wallet 클래스에 money 라는 원시 타입 변수를 직접 선언해서 사용하는 것이나
Money 클래스에 money 라는 원시 타입 변수를 선언해서 사용하는 것이나
결국 똑같은 것 아닌가요?
이 값을 왜 포장해야하죠?
원시 값 포장을 이야기하기 앞서, 가장 먼저 생길 수 있는 의문이다
위 의문에 대한 결론이자, 이 글에서 다루고자 하는 핵심 내용을 먼저 언급하고 이 글을 진행해보자
원시 값을 포장해 사용하면 비즈니스 로직의 책임과 역할을 분리할 수 있고,
객체지향적 자료구조에 한 걸음 다가갈 수 있다
핵심이 되는 내용을 설명하기 위해 이번 글에선 '초코 아이스크림' 과 '바닐라 아이스크림'을 예로 들어 코드를 개선해 나갈 것이다
초기의 '초코 아이스크림', '바닐라 아이스크림' 클래스이다
ChocoIceCream.class
public class ChocoIceCream {
private final static int MIN_PRICE = 1000;
private final String name;
private int price;
public ChocoIceCream(String name, int price) {
this.name = name;
this.price = price;
validateName();
validatePrice();
}
public void plusPrice(int price) {
this.price += price;
}
private void validateName() {
if (isEmptyName()) {
throw new IllegalArgumentException("이름은 반드시 있어야 합니다");
}
}
private void validatePrice() {
if (isLessThanMinPrice()) {
throw new IllegalArgumentException("가격은 반드시 " + MIN_PRICE +" 이상이어야 합니다");
}
}
private boolean isEmptyName() {
return name.trim().isEmpty();
}
private boolean isLessThanMinPrice() {
return price < MIN_PRICE;
}
}
VanillaIceCream.class
public class VanillaIceCream {
private final static int MIN_PRICE = 1000;
private final String name;
private int price;
public VanillaIceCream(String name, int price) {
this.name = name;
this.price = price;
validateName();
validatePrice();
}
public void plusPrice(int price) {
this.price += price;
}
private void validateName() {
if (isEmptyName()) {
throw new IllegalArgumentException("이름은 반드시 있어야 합니다");
}
}
private void validatePrice() {
if (isLessThanMinPrice()) {
throw new IllegalArgumentException("가격은 반드시 " + MIN_PRICE + " 이상이어야 합니다");
}
}
private boolean isEmptyName() {
return name.trim().isEmpty();
}
private boolean isLessThanMinPrice() {
return price < MIN_PRICE;
}
}
IceCreamService
public class IceCreamService {
public void iceCream() {
VanillaIceCream vanillaIceCream = new VanillaIceCream("banila", 5000);
ChocoIceCream chocoIceCream = new ChocoIceCream("choco",5000);
}
}
'바닐라 아이스크림' 과 '초코 아이스크림' 이 각자의 객체의 생성과 사용에 대한 비즈니스 로직을 캡슐화하여 그 책임을 가지고 있다
얼핏 보기에는 객체 지향적으로 잘 짜인 코드라고 느낄 수도 있다
하지만 구현된 코드를 보며 스스로에게 질문을 던져야 한다.
- 비즈니스의 코어에 해당하는 부분인 '이름' 과 '가격' 을 상징하는 무언가가 없어, '이름' 과 '가격' 에 대해 논할 때
'바닐라 아이스크림' 과 '초코 아이스크림'이 직접적으로 언급되어야 하는 것이 과연 바람직한 상황인가? - 대놓고 보이는 중복 메소드들이 존재하는데, 중복 메소드가 생긴 상황이 바람직한 상황인가?
위 두 질문에 대한 해답을 얻기 위해 현재 '원시 값'으로 존재하는 '이름' 과 '가격' 을 포장하여 새로운 객체로 만들고,
'초코 아이스크림' 과 '바닐라 아이스크림' 을 다시 보도록 하자
Name.class
public class Name {
private final String name;
public Name(String name) {
this.name = name;
validate();
}
public String getName() {
return this.name;
}
private void validate() {
if (isEmptyName()) {
throw new IllegalArgumentException("이름은 반드시 있어야 합니다");
}
}
private boolean isEmptyName() {
return name.trim().isEmpty();
}
}
Price.class
public class Price {
private final static int MIN_PRICE = 1000;
private int price;
public Price(int amount) {
this.price = amount;
validate();
}
public void add(int price) {
this.price += price;
}
public int getPrice() {
return this.price;
}
private void validate() {
if (isLessThanMinPrice()) {
throw new IllegalArgumentException("가격은 반드시 " + MIN_PRICE +" 이상이어야 합니다");
}
}
private boolean isLessThanMinPrice() {
return price < MIN_PRICE;
}
}
VanillaIceCream.class
public class VanillaIceCream {
private final Name name;
private final Price price;
public VanillaIceCream(Name name, Price price) {
this.name = name;
this.price = price;
}
public String getName() {
return this.name.getName();
}
public void addPrice(int price) {
this.price.add(price);
}
}
ChocoIceCream.class
public class ChocoIceCream {
private final Name name;
private final Price price;
public ChocoIceCream(Name name, Price price) {
this.name = name;
this.price = price;
}
public String getName() {
return this.name.getName();
}
public void addPrice(int price) {
this.price.add(price);
}
}
달라진 점을 살펴보자
- 코어에 해당하는 기능인 '이름' 과 '가격' 이 포장된 객체로 분리되었다
- 분리된 객체가 각자 자신이 해야 할 역할과, 책임을 객체 내부로 가져갔다
- '초코 아이스크림' 과 '바닐라 아이스크림' 은 '이름' , '가격' 에게 public 메소드를 통한 메시지를 전달하는 구조로 변경되었다.
이로서 비즈니스의 역할과 책임을 분리하고
'초코 아이스크림' 과 '바닐라 아이스크림' 은 각자의 비즈니스 로직에 더 집중할 수 있는 구조로 변경되었으며
객체지향적 설계에도 한걸음 더 다가서게 되었다.
마치며..
모든 원시 타입을 포장하는 것은 권장하지 않는다
객체로의 분리가 필요한 적재적소에 '원시 값 포장'을 사용하길 바란다
'개발 > Java' 카테고리의 다른 글
[JPA] Jakarta Spec(JSR 338) - (2)기본 키(PK) 및 Entity ID (0) | 2023.08.23 |
---|---|
[JPA] Jakarta Spec(JSR 338) - (1)Entity, Field, Access Type (0) | 2023.08.17 |
[Java] 상수 값에 행위를 정의하고 싶다면 - Constant Specific Method Implementation (0) | 2023.07.22 |
[Java] 인수값 유효성 검증 - 표현 계층 vs 응용 계층 (0) | 2022.10.23 |
일급 컬렉션 사용하기 [Java] (0) | 2022.04.22 |
댓글