OOP - 원시 값과 문자열을 포장하렴

Tae Yun Choi·2023년 4월 11일
2

개발새발 OOP

목록 보기
1/2
post-thumbnail

작년에 우테코 프리코스를 통해서 객체지향 생활 체조 원칙을 처음 접하게 되었다. 다시 한번씩 상기시키고 습관을 들이려는 의미에서 글을 하나씩 작성해보려 한다.
오늘은 그 중에서 ‘모든 원시 값과 문자열을 포장해라’ 라는 원칙에 대해 서술해보려한다.

💡

객체지향 생활 체조 원칙

규칙 1: 한 메서드에 오직 한 단계의 들여쓰기(indent)만 한다.

규칙 2: else 예약어를 쓰지 않는다.

규칙 3: 모든 원시값과 문자열을 포장한다.

규칙 4: 한 줄에 점을 하나만 찍는다.

규칙 5: 줄여쓰지 않는다(축약 금지).

규칙 6: 모든 엔티티를 작게 유지한다.

규칙 7: 3개 이상의 인스턴스 변수를 가진 클래스를 쓰지 않는다.

규칙 8: 일급 콜렉션을 쓴다.

규칙 9: 게터/세터/프로퍼티를 쓰지 않는다.


그냥 좀 쓰면 안 돼?

우선 해당 원칙을 고수하도록 추천하는 이유에 대해 알아보자

원시 타입은 스스로의 상태를 관리할 수 없다


class Person {
    String name;
    int age;

    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

Person person = new Person("cxxxtxxyxx", 28);

위 처럼 name과 age를 멤벼변수로 가지는 Person 클래스가 있다.
생성자를 통해 초기 값을 매개변수로 입력받아 인스턴스를 생성하는 코드이다.
얼핏 봤을 땐 문제가 되지 않는다.


Person person = new Person("cxxxtxxyxx", -1);

그럼 이런 상황이 발생하면 어떻게 할까?
논리적으로 age라는 변수에 음수가 할당되는 건 옳지 않다.
그래서 다음과 같은 생각을 할 것이다.


class Person {
    String name;
    int age;
    public Person(String name, int age) {
        validateAge(age);
        this.name = name;
        this.age = age;
    }
    
    
    private void validateAge(int age) {
        if (age < 0) {
            throw new IllegalArgumentException("올바르지 않은 나이입니다.");
        }
    }
}

생성자 호출 시 넘겨받은 매개변수의 유효성을 검사하는 방법으로 해결하는 것이다.
물론 나쁘지 않다.
하지만 조금 과장해서 만약 멤버변수가 100개라면?
모든 멤버 변수들에 대한 유효성을 체크하는 메소드를 하나의 클래스에서 모두 책임지도록 할 것 인가?
딱봐도 좋지 않아 보인다.

유지보수에도 좋지 않다


int money = 10000;

if (money >= Menu.getMostCheapPrice()) { 
	// paid menu
}

가지고 있는 돈이 가장 싼 메뉴의 가격보다 크거나 같으면 메뉴를 구매할 수 있다고 가정해보자.
코드가 변하지 않으면 문제 없다
하지만 해당 가게에서 이벤트를 열어서 메뉴들의 가격이 30% 할인한다고 가정해보자


int money = 10000;

if (money >= Menu.getMostCheapPrice() * 0.7) { 
	// paid menu
}

if문의 조건식이 변하게 되었다.
즉, 특정 조건이 추가되거나 요구사항에 변경이 생길 때마다 해당 조건식을 사용한 부분을 모두 수정해야한다.
이를 원시값을 포장함으로써 캡슐화를 통해 유연성을 확보할 수 있다.

그래, 그럼 어떻게 써야하는데?


class Person {
    Name name;
    Age age;
    public Person(String name, int age) {
        this.name = new Name(name);
        this.age = new Age(age);
    }
}

class Name {
    String name;

    public Name(String name) {
        validate(name);
        this.name = name;
    }

    private void validate(String name) {
        if (name.length() < 0) {
            throw new IllegalArgumentException("이름은 한 글자 이상이어야 합니다.");
        }
    }
}

class Age {
    int age;
    public Age(int age) {
        validate(age);
        this.age = age;
    }

    private void validate(int age) {
        if (age < 0) {
            throw new IllegalArgumentException("올바르지 않은 나이입니다.");
        }
    }
}

name 변수와 age 변수를 포장하였고, 이제 해당 값에 대한 유효성 검사를 독립적으로 분리시킬 수 있게 되었다. Person 클래스에 멤버변수가 추가된다고 해도 더 이상 멤버변수들에 대한 유효성 검사를 독박쓸 필요가 없게 되었고, 책임이 분산되었다.


class Money {
    int money;

    public Money(int money) {
        validate(money);
        this.money = money;
    }

    private void validate(int money) {
        if (money < 0) {
            throw new IllegalArgumentException("Money는 음수가 될 수 없습니다.");
        }
    }

    public boolean canPaidMenu() {
        return money >= Menu.getMostCheapPrice();
    }
}

/**********************/
Money money = new Money(10000);

if (money.canPaidMenu()) { 
	// paid menu
}

/**********************/
// ** 요구사항 추가 **
// 30% 할인
/**********************/

Money money = new Money(10000);

if (money.canPaidMenu()) { 
	// paid menu
}

public boolean canPaidMenu() {
   return money >= Menu.getMostCheapPrice() * 0.7;
}

캡슐화를 통해 해당 객체의 상태를 스스로 판단하도록 코드를 수정했다.
요구사항이 변경되어도 외부의 코드는 바뀌지 않고, 내부 코드만 수정하면 된다.
이로써 수정에 자유로워지고 기존 코드는 건드리지 않도록 수정에 용이해졌다.


확실히 조금 더 객체를 잘 활용할 수 있는 쪽으로 코드가 변하게 되는 것을 느낄 수 있었다. 사실 모든 객체, 모든 문자열을 포장하는 것은 너무 과하지 않을까 라는 생각도 들지만, 해당 값이 의미를 지니는 경우에는 포장해서 사용하는 것이 웬만해서 좋을 듯 싶다.

출처

https://tecoble.techcourse.co.kr/post/2020-05-29-wrap-primitive-type/

profile
hello dev!!

1개의 댓글

comment-user-thumbnail
2023년 5월 18일

좋은 글 잘 보고 갑니당

답글 달기