본문 바로가기
개발/Java

원시 값 포장 [Java]

by Mingvel 2022. 4. 27.

Primiitive

 

우아한 테크 캠프 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);
    }
}

 

달라진 점을 살펴보자

 

  1. 코어에 해당하는 기능인 '이름' 과 '가격'포장된 객체분리되었다
  2. 분리된 객체가 각자 자신이 해야 할 역할과, 책임을 객체 내부로 가져갔다
  3. '초코 아이스크림' 과 '바닐라 아이스크림' 은 '이름' , '가격' 에게 public 메소드를 통한 메시지를 전달하는 구조로 변경되었다.

 

이로서 비즈니스의 역할과 책임을 분리하고

'초코 아이스크림' 과 '바닐라 아이스크림' 은 각자의 비즈니스 로직에집중할 수 있는 구조로 변경되었으며

객체지향적 설계에도 한걸음 더 다가서게 되었다.

 


마치며..

 

모든 원시 타입을 포장하는 것권장하지 않는다

 

객체로의 분리가 필요한 적재적소에 '원시 값 포장'을 사용하길 바란다

 

 

 

반응형

댓글