객체지향을 공부하던 중, 객체지향 생활체조 규칙을 알게되었고, 다음과 같은 내용을 봤다.
규칙9: 게터 사용 금지
하지만 클래스를 처음 배울때 즉, 자바 빈 설계 규약에 따르면, 멤버변수의 수정을 막기 위해 직접 참조하기보다는 멤버변수의 접근제어자를 private로 하고, getter를 사용해야 한다! 라고 배웠던 기억이 나 혼란이 온다.
위 규칙의 이유를 알아보자.
객체지향 프로그래밍에서, 객체는 캡슐화가 된 상태, 외부에 노출되어있는 행동을 가지고, 다른 객체와 메세지를 주고받으며 협럭하는 것이다.
객체는 메세지를 받으면 그에 따른 행동을 하고, 내부의 상태값도 변경한다.
하지만, 멤버변수의 getter를 생성해 놓고 그 값을 외부에서 꺼내 로직을 수행한다면, 객체가 행동을 갖고 있는 형태도 아니고, 메세지를 주고받는 형태도 아니다.
디미터의 법칙에 따르면, 모듈은, 객체의 속사정을 몰라야 한다. 그저 메세지를 던지고 메세지를 받을 뿐이여야 한다.
상태를 가지는 객체를 추가했다면 객체가 제대로 된 역할을 하도록 구현해야 한다.
객체가 로직을 구현하도록 해야한다.
상태 데이터를 꺼내 로직을 처리하도록 구현하지 말고 객체에 메시지를 보내 일을 하도록 리팩토링한다.
즉, 객체가 멤버변수를 반환하고, 외부에서 로직을 수행하는것 보단, 객체 내부의 로직에서 수행되어야 할 값들을 전달해주고, 객체 내부에서 로직을 수행한 후 결과값을 반환하는것이 객체지향의 특징인 캡슐화
에 더깝다는 뜻이다.
객체에서 getter를 쓰는 보편적인 상황은 다음과 같다.
1번의 경우, 객체에서 값을 꺼내서 로직을 수행하지 말고, 로직 수행의 책임을 객체에게 부여하면 된다.
public class Monster {
private int power;
public Monster(int power) {
this.power = power;
}
public int getPower() {
return power;
}
}
public Monster powerCompare(Monster m1, Monster m2) {
return m1.getPower() - m2.getPower();
}
//...
Monster m1 = new Monster(5);
Monster m2 = new Monster(3);
System.out.println(powerCompare(m1, m2)); // 2
위 함수는 객체의 멤버 변수 값인 power 를 직접 꺼내서 객체 외부에서 비교하고 있다.
이는 객체의 상태가 외부에 노출되었다고 볼 수 있다.
비교 로직을 Monster 본인에게 옮겨보자.
public class Monster implements Comparable<Monster> {
private int power;
public Monster(int power) {
this.power = power;
}
@Override
public int compareTo(Monster other) {
return this.power - other.power;
}
}
Monster m1 = new Monster(5);
Monster m2 = new Monster(3);
System.out.println(m1.compareTo(m2)); // 2
getPosition() 없이 둘의 속성를 비교했다. Monster 에서 Comparable을 상속받아 메소드를 구현했다.
이런식으로 로직을 객체 내부에서 구현하면 게터를 사용할 필요가 없어진다.
2번(객체의 값이 올바른 값인지 검사)도 마찬가지로 객체 본인의 값이 올바른지 검사하는 책임은 객체 본인에게 있다. Validator 클래스를 따로 구현하지 않아도 된다.
3번(객체의 값을 꺼내서 출력) 같은 단순 출력만을 위한 용도정도는 허용된다.
하지만 조심하자. Collection 같은 형태는 getter로 반환할 경우, 외부에서 값 수정이 가능하다. Immutable 한것으로 반환하자.
-> Collections.unmodifiableList()
사용 으로 해결