두 달 전쯤, 회사에서 개발한 자바 서비스를 이용하여 특정 클라이언트를 개발한 적이 있다. 아직 개발 중인 자바 서비스이므로 메서드명 뿐만 아니라 파라미터 등이 수정사항에 맞춰 빈번하게 변경되고 있었다.
하지만 그때마다 클라이언트 쪽이 제대로 동작하지 않아 한번 고생을 하게 되었고, 그 후로 public으로 선언된 클래스에 변경을 꺼리게 되었다.
어찌 보면 자바에 기초를 까먹고 있던 것이다. 자바를 배웠을 때 항상 중요하게 들은 정보 은닉 또는 캡슐화. 이펙티브 자바 규칙13에서는 소프트웨어 설계의 기본적인 원칙 가운데 하나인 캡슐화에 관해서 설명한다.
캡슐화 (정보은닉)
캡슐화는 구현 세부사항을 API 뒤쪽에 숨겨 API를 사용하는 모듈(클라이언트)이 API가 내부적으로 어떤 행동을 하는지 신경을 쓰지 않도록 한다.
자바에서는 캡슐화를 실현할 수 있도록 private, protected, public 등의 접근 제어를 제공한다.
규칙13을 지키기 위한 원칙은 단순하다. 최대한 클래스와 멤버 변수들은 private로 선언하는 것이다. 그리고 클라이언트에게 제공하는 API만 public 또는 protected로 제공하는 것이다. 만약 public 또는 protected로 제공하는 API를 수정할 경우 내 경험처럼 클라이언트의 코드가 제대로 동작하지 않을 것이다. 그러므로 클라이언트 코드를 보장하기 위해 해당 API를 계속 지원해야 한다. (대부분 그렇지는 않지만..)
극단적인 예를 들면 아래와 같다.
PackagePrivateUtil에 add 메서드를 이용해서 SmallCalculator는 int 타입의 덧셈과 뺄셈을 제공한다. 클라이언트는 두 개의 클래스가 모두 public이므로 모두 접근할 수 있다.
개발자는 작은 수가 아닌 큰 수의 덧셈 뺄셈을 지원하는 클래스를 위해 PackagePrivateUtil에 add 메서드의 인자와 리턴 타입을 BigInteger로 변경하였다. (극단적인 예이다. 사실 오버로딩하면 해결된다.)
SmallCalculator는 int 타입의 덧셈과 뺄셈을 계속 제공해야 하므로 내부 구현만 변경한다. 그러므로 기존 SmallCalculator를 사용하는 클라이언트의 코드는 깨지지 않는다. 하지만 PackagePrivateUtil의 add 메서드를 사용하는 클라이언트는 인자와 리턴 타입이 변경되어 코드가 깨지게 된다.
PackagePrivateUtil은 내부적으로 언제든지 변경될 수 있는 코드이다. 이런 코드는 클라이언트에게 제공해서는 안 되며 제공했다면 변경하면 안 된다. 최대한 제공하지 않는 것이 이 규칙13이 말하고자 하는 것이며 그러므로 PackagePrivateUtil은 package-private 클래스로 선언하여 해결한다.
package-private를 사용하는 클래스가 하나뿐일 경우
package-private로 만든 클래스를 사용하는 클래스가 하나뿐일 경우, 그 클래스 안에 package-private 클래스를 중첩 클래스로 만드는 것이 좋다. (규칙22)
package-private 클래스는 동일한 패키지 안에 있는 클래스가 모두 사용할 수 있지만, 중첩 클래스로 만들 경우, 중첩 클래스의 바깥 클래스만 접근할 수 있다. 따라서 특정 클래스를 사용하는 클래스가 하나뿐일 경우 굳이 클래스 파일을 따로 만들 필요 없이 구현하면 된다.
위 예제에 PackagePrivateUtil이 SmallCalculator에서만 사용된다면 다음과 같이 구현하면 된다.
클래스 멤버(필드, 메서드, 중첩클래스, 중첩인터페이스)의 접근 권한
클래스 멤버의 접근 권한은 private, package-private, protected, public이 있다.
protected, public으로 선언한 멤버들은 클라이언트가 접근할 수 있으므로 영원히 유지해야 한다. 따라서 최대한 권한을 줄이는 게 좋다. 하지만 접근 권한을 줄일 수 없는 경우가 있다. 상위 클래스 메서드를 오버라이딩할 때는 원래 메서드의 접근 권한보다 낮은 권한을 설정할 수 없다. (이 규칙 때문에 인터페이스 메서드들은 모두 public이다.)
객체 필드변수는 절대로 public으로 선언하면 안 된다. 필드를 public으로 선언하면 필드에 저장될 값을 제한할 수 없게 되어 그 필드에 관계된 불변식을 강제할 수 없다. 그러므로 getter, setter와 같은 접근자 메서드를 사용하는게 좋다.
public으로 선언해도 되는 예외가 있는데 자주 사용하는 상수값을 정의하는 public static final 형식이다.
참고 : public static final 배열 선언
위 코드와 같이 길이가 0이 아닌 배열 상수 필드는 배열 레퍼런스는 변경하지 못하지만 배열 레퍼런스안에 존재하는 값은 변경할 수 있다. 따라서 public static final 배열 필드를 두거나, 배열 필드를 반환하는 접근자를 정의하면 안 된다.
따라서 클라이언트에게는 배열 필드의 상수 값을 복사해서 넘겨주는 방식으로 이 문젤르 해결한다.
방법 1.
방법 2.