홈

현이의 개발 이야기

Getter / Setter 제대로 사용하기

Java2024. 06. 14. 15:57

자바에서 접근 제어자를 처음 배울 때 Getter와 Setter에 대해서도 같이 배우게 된다.
하지만 getter와 setter를 잘못 설명하고 있는 경우가 많은 것 같아 이들을 어떤 경우에 사용해야 하는지에 대해 적어보고자 한다.

잘못된 예제

많은 예제들이 캡슐화라고 하면서 멤버 필드를 private으로 만들고, getter, setter 메서드를 통해 해당 필드에 접근하게 한다.
public class Meaningless {
  private int value = 0;
  
  public int getValue() {
  	return value;
  }
  
  public void setValue(int value) {
  	this.value = value;
  }
}
위의 코드는 멤버 필드를 public으로 두는 것과 다를 바 없다. 외부에서는 해당 값을 그대로 읽고, 값을 덮어쓸 수 있다.
public class Same {
  public int value = 0;
}
오히려 메서드 호출 오버헤드가 발생하고 불필요한 코드가 생기는 셈이니 더 나쁘다고도 할 수 있다.
Getter와 setter는 메서드로서의 역할을 다 할 때 그 의미가 생긴다.

외부에서 값을 덮어쓰는 것을 막을 때

게임에서 점수 시스템을 만든다고 해보자.
코인을 획득하면 1점, 적을 처치하면 5점이 오른다.
이를 관리하는 Score 클래스는 다음과 같이 작성할 수 있다.
public class Score {
    private int score = 0;

    public int getScore() {
        return score;
    }

    public void coinEarned() {
        score += 1;
    }

    public void enemyKilled() {
        score += 5;
    }
}
score는 클래스 내부에 숨겨두고, score를 증가시킬 수 있는 메서드만 외부에 공개함으로써 score를 직접적으로 변경하지 못하게 차단하였다. 이를 통해 score는 이 클래스 내부에서만 관리한다는 제약을 걸어, 외부에서 예상하지 못하게 값을 수정하는 일에 대해 걱정하지 않아도 되게 되었다.

값을 제한할 때

특정 범위 내의 값을 관리한다고 생각해보자. 이 클래스는 다음과 같이 작성할 수 있다.
public class ClampedValue {
    public final int min;
    public final int max;

    private int value = 0;

    public ClampedValue(int min, int max) {
        this.min = min;
        this.max = max;
    }

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        if (value < min) {
            this.value = min;
        } else if (value > max) {
            this.value = max;
        } else {
            this.value = value;
        }
    }
}
한 멤버 필드에 대해서 getter와 setter가 있지만, setter에서 해당 필드의 값을 제한하는 처리를 한다.

객체 필드일 때

필드가 객체인 경우, 그리고 해당 객체가 불변 객체가 아닐 경우 이를 외부에 그대로 노출하는 것은 매우 위험하다.
이를 해결하기 위해서 getter에서 객체를 복사하여 넘겨줄 수 있다.
숫자들의 합을 구하면서 그 과정을 기록하는 RecordedSum 클래스를 살펴보자.
public class RecordedSum {
  private final List<Integer> history = new ArrayList<>();
  private int sum = 0;

  public List<Integer> getHistory() {
    return Collections.unmodifiableList(history);
  }

  public int getSum() {
    return sum;
  }

  public void add(int value) {
    history.add(value);
    sum += value;
  }
}
이 예제에서는 List인 history를 외부에서 요구할 때 불변 리스트로 변환하여 반환한다.
이렇게 함으로써 외부에서 history를 함부로 수정할 수 없게 하여 history는 RecordedSum 클래스 내부에서만 관리할 수 있도록 할 수 있다.

이렇게 getter와 setter가 의미 있게 사용될 수 있는 경우들을 살펴보았다.
public 멤버가 나쁜 것이 아니다.
해당 멤버의 값이 변함으로써 발생하는 사이드 이펙트가 없고, 외부에서 자유롭게 변경하도록 의도된 것이라면 public으로 공개해주는 것이 좋은 설계가 될 수 있다.
public이 될 수 있는 멤버를 굳이 private으로 숨기면서 의미 없는 getter와 setter를 사용하는 일이 없도록 하자.
문자열 해시를 쓰면 안되는 이유 (HashMap, HashSet)
다음 포스트문자열 해시를 쓰면 안되는 이유 (HashMap, HashSet)
이전 포스트자바로 분수 나타내기
자바로 분수 나타내기
댓글 0

로그인이 필요합니다.
로그인