[이펙티브 자바] 클래스와 인터페이스 Item16 - public 클래스에서는 public 필드가 아닌 접근자 메서드를 사용하라
객체 지향 방법론(Object Oriented Programming : OOP)의 기본 개념은
공통적으로 사용되는 부분을 미리 추상화 해놓는 것이다.
그것을 필요에 따라 구체화시켜 사용하는 것은 코드의 재사용성을 높이며,
그것은 곧 OOP의 지향점이 된다.
자바에서는 추상화의 기본 단위로 클래스(Class)와 인터페이스(Interface) 를 정의하고 있고, 이는 곧 자바의 심장과도 같다.
"4장 - 클래스와 인터페이스" 에서는,
쓰기 편하고, 견고하며, 유연한 클래스와 인터페이스를 만드는 방법에 대해 내용을 서술한다.
- Item15. 클래스와 멤버의 접근 권한을 최소화하라.
- Item16. public 클래스에서는 public 필드가 아닌 접근자 메서드를 사용하라.
- Item17. 변경 가능성을 최소화하라.
- Item18. 상속보다는 컴포지션을 사용하라.
- Item19. 상속을 고려해 설계하고 문서화하라. 그러지 않았다면 상속을 금지하라.
- Item20. 추상 클래스보다는 인터페이스를 우선하라.
- Item21. 인터페이스는 구현하는 쪽을 생각해 설계하라.
- Item22. 인터페이스는 타입을 정의하는 용도로만 사용하라.
- Item23. 태그 달린 클래스보다는 클래스 계층구조를 활용하라.
- Item24. 멤버 클래스는 되도록 static으로 만들라.
- Item25. 톱레벨 클래스는 한 파일에 하나만 담으라.
<"public 클래스에서는 public 필드가 아닌 접근자 메서드를 사용하라.">
# public 클래스의 경우
시작과 함께 먼저 아래의 예시를 봐보자.
class Point { public double x; public double y; }
간혹가다 위처럼 인스턴스 필드만을 모아놓는 경우가 생긴다.
(작가는 "퇴보한" 이라는 극적인 워딩을 사용했다....)
로직의 유무를 말하는게 아니다.
일반적으로 엔티티를 정의할 때도 저렇게 필드만 선언되니까 말이다.
문제는 바로 저 필드만 딱 있을때 발생한다.
모두 public으로 선언되어 있기 때문에,
다른 컴포넌트에서 해당 클래스 데이터 필드에 직접 접근이 가능하다.
그 말은, 캡슐화(Encapsulation)가 전혀 안되기 때문에 그에 따른 장점도 얻을 수 없다는 말이다.
사실 장점을 얻을 수 없는 차원이라기 보다, 많은 단점과 위험성이 존재한다고 하는게 맞는것 같다.
<단점>
- 캡술화의 이점이 없다.
- API를 수정하지 않고는 내부 표현을 바꿀 수 없다.
- 불변식을 보장할 수 없다.
- 외부에서 필드에 접근할 때 부수 작업을 수행할 수도 없다. (Thread가 불안전하다, Race Condition)
그래서 위 코드는 아래와 같이 바꾸는게 정석이다.
class Point { private double x; private double y; public Point(double x, double y) { this.x = x; this.y = y; } public double getX() { return x; } public double getY() { return y; } public void setX(double x) { this.x = x; } public void setY(double y) { this.y = y; } }
모든 데이터 필드의 접근 가능성을 private으로 바꾼다.
그런 이후에 데이터 필드에 접근할 수 있는 별도의 메서드를 생성한다.
자바에서는 이러한 것을 접근자(Getter, Setter)라고 칭한다.
위와 같이 패키지 바깥에서 접근할 수 있는 클래스에 접근자를 제공하는 방법을 취하면,
클래스 내부 표현 방식을 언제든 유현하게 바꿀수 있게 된다.
<Comment.>
- 여기서 "내부 표현 방식"이 유연해진다 라는 의미는 "변수명"이라고 바꿔보면 조금 쉽게 이해가 가능했다.
- 접근자를 이용하지 않고 필드를 직접 노출하게 되면, 클라이언트는 해당 변수명을 그대로 이용하게 된다.
- 만약 내가 이 변수명을 바꾸려고 하면? 그것을 이용하는 모든 클라이언트가 다 바꿔야 하기 때문에 사실상 바꾸는게 불가능해진다.
- 그렇지만 클라이언트가 접근자 (Getter, Setter)를 이용하고 있다면 내부적으로 얼마든지 변수명을 바꿀수 있다는 것이다.
# private-package, private 클래스의 경우
그렇다면 클래스가 public이 아닌, private-package나 private 중첩 클래스라면 어떨까?
이 경우엔 데이터 필드를 노출하던 안하던 별로 상관이 없다.
(어차피 외부에서 접근도, 수정도 못한다.)
이때는 그냥 본분을 다해 그 클래스가 구현하고자 하는 기능만 잘 구현해내면 된다.
실제로 이것은 클래스 선언 면에서나, 클라이언트 코드 면에서나 접근자보다 훨씬 깔끔해진다.
(사실 클라이언트 코드에서야 접근자를 사용하는 것보다 직접 필드를 사용하는게 더 깔끔하다.)
<Comment.>
- 아마도 이런 의문이 드는 사람이 있을 것이다.
- "이것도 그럼 변수명 바꾸면 클라이언트도 다 바꿔야 하는데?"
- public 클래스의 경우 공개 API이기 때문에 전세계의 모든 사람이 클라이언트지만, package-private은 그 패키지 내에서만 클라이언트가 존재한다.
- 선자는 바꾸는게 불가능하고, 후자는 바꾸는게 어렵지 않다. 아예 가능성 자체가 다르다.
중첩된 private 클래스의 경우는 더 쉽다.
어차피 그 클래스를 사용할 수 있는건 바로 바깥 클래스 하나이기 때문에 클라이언트가 될 수 있는 대상이 훨씬 좁다.
내부 표현 수정이 더욱 용이하다.
# 만약 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; } }
위는 입력으로 들어오는 시간이 유효한 시간임을 보장하는 클래스이다.
필자의 코멘트로 글을 마무리 한다.
Item16 정리
- public 클래스는 절대 가변 필드를 직접 노출해서는 안된다.
- 불변 필드라면 노출해도 덜 위험하지만 완전히 안심할 수는 없다.
- package-private이나 중첩 private 클래스는 노출하는게 나을 때도 있다.
Author And Source
이 문제에 관하여([이펙티브 자바] 클래스와 인터페이스 Item16 - public 클래스에서는 public 필드가 아닌 접근자 메서드를 사용하라), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@holidenty/이펙티브-자바-클래스와-인터페이스-Item16-public-클래스에서는-public-필드가-아닌-접근자-메서드를-사용하라저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)