캡슐화의 이점을 제공하지 못하는 클래스
public 클래스에서 데이터를 직접 필드로 노출시키면 캡슐화의 이점을 제공하지 못하게 된다. 아래와 같은 경우가 대표적이다.
class Point {
public double x;
public double y;
}
이러한 클래스에는 몇 가지 문제가 있다:
- 내부 표현을 유연하게 변경할 수 없다: 데이터 필드가 공개되어 있어 내부 구현을 수정할 때 API도 함께 수정해야 한다.
- 불변식을 보장할 수 없다: 외부에서 필드를 직접 수정할 수 있어 객체의 일관성을 유지하기 어렵다.
- 필드 접근 시 부수작업을 수행할 수 없다: 필드에 접근할 때 추가적인 로직(검증, 로그 기록 등)을 수행할 수 없다.
객체 지향 방식으로 캡슐화한 클래스
위 문제를 해결하려면 필드를 모두 private으로 변경하고 public 접근자(getter)와 변경자(setter)를 제공해야 한다.
class Point {
private double x;
private double y;
public Point(double x, double y) {
this.x = x;
this.y = y;
}
public double getX() {
return this.x;
}
public double getY() {
return this.y;
}
public void setX(double x) {
this.x = x;
}
public void setY(double y) {
this.y = y;
}
}
이렇게 접근자와 변경자를 제공함으로써 클래스 내부의 구현을 유연하게 변경할 수 있다. 또한, 필드 접근 시 추가적인 로직을 추가할 수 있어 객체의 일관성을 유지하는 데 도움이 된다.
package-private 클래스와 private 중첩 클래스
package-private 클래스 혹은 private 중첩 클래스라면 데이터를 필드로 직접 노출해도 큰 문제가 되지 않는다. 중요한 점은 클래스가 표현하려는 추상 개념을 올바르게 표현하는 것이다.
public class TopPoint {
private static class Point {
public double x;
public double y;
}
public Point getPoint() {
Point point = new Point();
point.x = 1.2;
point.y = 5.3;
return point;
}
}
private또는package-private클래스라면 외부에서는 해당 클래스의 필드에 접근할 수 없다. 하지만 탑 클래스 내부에서는 필드를 조작할 수 있다.- 이러한 방식은 클래스 선언과 클라이언트 코드 면에서 접근자 메서드를 사용하는 것보다 깔끔하다.
- 클라이언트 코드가 클래스 내부에 묶이기는 하지만, 이는 해당 패키지 안에서만 동작하는 코드이므로 패키지 바깥 코드를 손대지 않고도 데이터 표현 방식을 변경할 수 있는 유연성을 제공한다.
public 클래스 필드 직접 노출 사례
자바 플랫폼 라이브러리에서도 public 클래스의 필드를 직접 노출하는 잘못된 사례가 있다. 대표적으로 java.awt 패키지의 Point와 Dimension 클래스가 있다.
package java.awt;
public class Point {
public int x;
public int y;
public Point() {
this(0, 0);
}
public Point(int x, int y) {
this.x = x;
this.y = y;
}
}
...
Point point = new Point(10, 20);
point.x = 30; // 외부에서 객체의 내부 상태를 변경
public필드가 불변이라면 그나마 단점이 줄어들지만, 여전히 좋은 설계는 아니다.public필드에final을 추가하여 불변으로 만들면 불변식은 보장되지만, 표현 방식을 변경할 수 없고 필드 접근 시 부수작업을 수행할 수 없다는 단점은 여전하다.
불변 필드를 노출한 public 클래스의 예
public final class Time {
private static final int HOURS_PER_DAY = 24;
private static final int MINUTES_PER_HOUR = 60;
public final int hour;
public final int minute;
public Time(int hour, int minute) {
if (hour < 0 || hour >= HOURS_PER_DAY)
throw new IllegalArgumentException("Hour: " + hour);
if (minute < 0 || minute >= MINUTES_PER_HOUR)
throw new IllegalArgumentException("Min: " + minute);
this.hour = hour;
this.minute = minute;
}
}
final로 불변성을 보장해도 API를 변경하지 않고는 표현 방식을 바꿀 수 없고, 필드를 읽을 때 부수작업을 수행할 수 없다는 단점이 남아 있다.
public 필드를 노출할 때의 문제점
public 필드를 노출하면 어디서 어떻게 값이 변경될지 알 수 없다. 따라서 안전하게 사용하기 위해 객체를 사용하는 쪽에서 값을 복사해서 사용하는 것이 좋다.
public class Main {
public static void main(String[] args) {
Point point = new Point();
point.x = 10;
point.y = 20;
doSomething(point);
System.out.println(point.x);
System.out.println(point.y);
}
private static void doSomething(Point point) {
Point localPoint = new Point();
localPoint.x = point.x;
localPoint.y = point.y;
}
}
객체를 안전하게 사용하기 위해서는 복사하여 사용하는 것이 필요하다. public으로 공개된 클래스는 어디서 사용될지 알 수 없기 때문에, 이를 컨트롤하기가 어렵다. 따라서 getter와 setter 메서드를 제공해 필드에 접근하도록 해야 한다.
'Programming Language > Java' 카테고리의 다른 글
| [이펙티브 자바]Item.18-상속보다는 컴포지션을 사용하라 (1) | 2024.12.11 |
|---|---|
| [이펙티브 자바]Item.17-변경 가능성을 최소화하라 (0) | 2024.12.02 |
| [이펙티브 자바]Item.15-클래스와 멤버의 접근 권한을 최소화해라 (0) | 2024.11.26 |
| [이펙티브 자바]Item.14-Comparable을 구현할지 고려하라. (0) | 2024.11.25 |
| [이펙티브 자바]Item.13-clone 재정의는 주의해서 진행하라 (1) | 2024.11.20 |