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