홈

현이의 개발 이야기

Getter / Setter 제대로 사용하기

Java 2024. 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를 사용하는 일이 없도록 하자.
댓글 0

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