[OOP] 무분별한 Getter/Setter를 지양하라
무분별한 Getter/Setter를 지양하라?
우리는 보통 개발을 할 때 getter와 setter 메서드를 생성하여 자주 사용하곤한다. 그리고 매우 편리하다. 하지만 이렇게 모든 멤버변수에 대해 getter/setter를 생성하여 사용하는 것이 좋을까?
객체지향 설계 시에 이를 지양하는 것이 좋다고 한다. 왜 그럴까?
Setter를 지양하라?
개발은 혼자가 아니다
setter를 무분별하게 사용하면 다른 개발자가 해당 코드를 봤을 때 값을 변경한 의도를 파악하기 힘들어진다.
public static void main(String[] args) {
Lotto lotto = new Lotto(List.of(1, 4, 12, 32, 40, 41));
lotto.setNumbers(List.of(13, 14, 20, 31, 36, 45));
}
위 코드를 보면 Lotto를 생성한 후 setter메서드를 통해 번호를 다시 부여한 것을 확인할 수 있다. 하지만 이렇게 setter를 통해 값을 넣으면 어떤 의도로 넣은 것인지 명확히 알 수 없다.
객체의 일관성을 유지하자
setter를 사용하면 언제든 외부에서 객체 상태를 바꿀 수 있게 된다. 예시로, 위에서 본 코드처럼 얼마든 Lotto가 가지고 있는 번호들을 바꿀 수 있게 된다. 이렇게 되면 객체의 일관성을 유지하기 힘들어지고 어디서 어떤 상태가 변경되는지 파악하기 힘들어진다. 즉, 코드의 가독성이 떨어질 수 있다.
Getter를 지양하라?
각자의 책임을 지게하자
객체는 각자 알맞는 행동과 책임을 갖고 있으며, 객체의 상태는 캡슐화되어 외부에 노출시키지 않는다. 또 객체는 다른 객체와 메시지를 주고 받으며 협력한다. 객체는 메시지를 받으면 그에 따른 행동(로직)을 수행하게 되고, 필요하다면 객체 스스로 내부의 상태(값)도 변경한다.
그런데 모든 멤버변수에 getter를 생성해놓고 상태 값을 꺼내 그 값으로 객체 외부에서 행동한다면, 그 객체의 책임이 다른 곳에서 수행되고 외부에서 상태를 변경하는 상황이 생길 수 있다.
객체는 각자 책임을 가지고 행동을 수행하며 서로 메시지를 통해 협력해나가는 관계여야한다. 하지만 위와 같은 형태는 객체 간의 협력이라고 보기 힘들다.
또한 getter를 무분별하게 사용하면 "메시지 체인"이라는 악취가 나게 된다.
메시지 체인이란, 다음과 같이 레퍼런스를 따라 계속해서 메서드 호출이 이어지는 코드를 말한다.
object.getChild().getContent().getItem().getTitle();
이렇게 되면 코드의 가독성도 떨어지는 효과도 볼 수 있다.
다음은 로또 번호를 비교하는 코드 예시이다.
class Lotto {
private List<Integer> numbers;
public Lotto(List<Integer> numbers) {
this.numbers = new ArrayList(numbers);
}
public int getNumber(int index) {
return numbers.get(index);
}
}
public class Client {
private static final int LOTTO_NUMBER_LENGTH = 6;
public static void main(String[] args) {
Lotto lotto = new Lotto(List.of(3, 7, 15, 21, 25, 41));
boolean exist = false;
for (int i = 0; i < LOTTO_NUMBER_LENGTH; i++) {
if (lotto.getNumber(i) == 15) {
exist = true;
break;
}
}
}
}
위 로직을 보면 lotto에서 getter로 값을 꺼내 비교하는 것을 볼 수 있다. 하지만 여기서 두 번호를 비교하는 로직을 수행하는 것이 맞을까?
위에서 말했듯이 객체는 각자 스스로 알맞은 책임을 가지고 행동해야 한다.
하지만 위 로직은 값을 판단하는 책임이 Lotto가 가지고 있는게 아닌 외부에 있다. 이를 Lotto 내부로 옮겨 Lotto가 해당 값이 포함되어 있는지 체크하도록 해보자.
class Lotto {
private List<Integer> numbers;
public Lotto(List<Integer> numbers) {
this.numbers = new ArrayList(numbers);
}
public boolean contains(int number) {
return numbers.contains(number);
}
}
public class Client {
private static final int LOTTO_NUMBER_LENGTH = 6;
public static void main(String[] args) {
Lotto lotto = new Lotto(List.of(3, 7, 15, 21, 25, 41));
boolean exist = lotto.contains(15);
}
}
이렇게 해서 외부에서는 해당 값이 들어있는지 메시지를 보내고 Lotto는 포함되어 있는지 여부를 반환하도록 했다. 이렇게 함으로써 좀 더 객체스러운 코드가 됐다.
마무리
getter를 무조건 사용하지 말라는 것이 아니다. 당연히 getter를 쓰지 않고 구현하기 힘든 것이 있을 것이다. 예를 들어, 출력이나 데이터를 이동하기 위해 쓰는 DTO 등에서는 사용이 어느 정도 허용된다. 하지만 위에서와 같이 너무 무분별한 사용을 피하고 되도록 객체의 책임을 부여하고 메시지를 통해 협력하고 행동할 수 있도록 설계하는 자바 개발자가 되어보자.
'Software Engineering > OOP' 카테고리의 다른 글
[OOP] 원시 타입을 포장하라 (0) | 2023.03.15 |
---|---|
[OOP] 상속보다는 조합을 사용하자 (0) | 2023.03.15 |
[OOP] instanceof의 사용을 지양하라 (0) | 2023.03.14 |
댓글
이 글 공유하기
다른 글
-
[OOP] 원시 타입을 포장하라
[OOP] 원시 타입을 포장하라
2023.03.15 -
[OOP] 상속보다는 조합을 사용하자
[OOP] 상속보다는 조합을 사용하자
2023.03.15 -
[OOP] instanceof의 사용을 지양하라
[OOP] instanceof의 사용을 지양하라
2023.03.14