[도서] Effective Java - Item 15. 클래스와 멤버의 접근 권한을 최소화하라
이펙티브 자바 3판을 읽으면서 내용을 정리하는 포스트입니다. 혹시 틀린 부분이나 잘 못 설명한 부분이 있으면 댓글로 남겨주시면 수정하도록 하겠습니다.
Item 15. 클래스와 멤버의 접근 권한을 최소화하라
잘 설계된 컴포넌트와 그렇지 못한 컴포넌트의 가장 큰 차이점은 클래스 내부 데이터와 구현 정보를 외부로부터 얼마나 잘 숨겼고 꼭 필요한 정보들만 공개를 했는가다. 잘 설계된 컴포넌트일수록 내부 구현을 완벽히 숨겨서 구현과 API를 분리한다. 이처럼 구현과 API가 분리되어있다면 사용자 입장에서는 API만 신경쓰면되고 그 결과에만 집중하면된다. 이런 개념을 정보 은닉
, 혹은 캡슐화
라고 한다.
정보은닉의 장점은 대부분 컴포넌트를 서로 독립시켜 개발,테스트, 최적화, 적용, 분석, 수정을 개별적으로 할 수 있게 하는것과 연관되있다.
정보 은닉(캡슐화)의 장점
- 시스템 개발 속도를 높힌다.
- 여러 컴포넌트(개발, 테스트, 최적화)가 개별적으로 개발 가능하기 때문이다.
- 시스템 관리 비용을 낮춘다.
- 각 컴포넌트를 더 빨리 파악하여 디버깅이 가능하고 컴포넌트 교체도 부담이 적기 때문이다.
- 성능 최적화에 도움을 준다.
- 정보 은닉 자체가 성능을 높혀주는 것은아니지만, 완성된 시스템에서 최적화 할 컴포넌트를 정하고 다른 컴포넌트에 영향을 주지 않고 해당 컴포넌트만 최적화 할 수 있기 때문이다.
- 소프트웨어 재사용성을 높힌다.
- 외부에 거의 의존하지 않고 독자적으로 동작이 가능한 컴포넌트는 다른 시스템에서도 바로 적용이 가능하기 때문이다.
- 큰 시스템을 제작하는 난이도를 낮춰준다.
- 시스템이 완성되지 않은 상태에서도 개별 컴포넌트의 동작을 검증할 수 있기 때문이다.
자바의 정보 은닉을 위한 장치
접근 제어 메커니즘을 이용해 클래스, 인터페이스, 멤버의 접근성(접근 허용 범위)를 명시한다. 각 요소의 접근성은 그 요소가 선언된 위치와 접근 제한자(private, protected, public)로 정해진다. 그리고 이런 접근 제한자를 활용하여 정보 은닉을 할 수 있다.
모든 클래스와 멤버의 접근성을 가능한 한 좁혀야 한다.
소프트웨어가 올바르게 동작하는 한 가장 낮은 접근 수준을 부여해야 한다. 톱레벨 클래스와 인터페이스는 package-private, public 을 부여할 수 있다. public으로 할 경우 공개 API가 되며, package-private가 되면 해당 패키지 내에서만 쓸 수 있다.
패키지 외부에서 쓸 일이 없다면 package-private로 선언하는 게 좋다. API가 아닌 내부 구현이기에 수정에서 자유로워져서 클라이언트에 피해 없이 다음 릴리즈에서 수정, 교체, 제거가 가능하다. public으로 선언하여 공개 API로 사용할 경우 영원히 하위 호환을 위해 관리해줘야 하며 수정 변경이 힘들어진다.
private static 중첩 이용
클래스를 private static으로 중첩시키면 바깥 클래스 하나에서만 접근할 수 있다.
클래스의 공개 API를 설계 후 모든 멤버를 private으로 만들고, 같은 패키지 내부의 다른 클래스가 접근해야 하는 멤버에 한하여 package-private으로 풀어주면 API 영향을 주지 않으면서 접근성을 좁힐 수 있다.
접근제어자의 접근 가능성
-
같은 클래스 안에서 접근 같은 패키지 안에서 접근 상속받은 클래스에서 접근 외부에서 접근 public O O O O protected O O O X package-private O O X X private O X X X
멤버 접근성을 좁히지 못하는 제약
상위 클래스의 메소드를 하위 클래스에서 재정의하는 경우 이 메소드의 접근 수준을 상위 클래스보다 좁게 설정할 수 없다. 상위 클래스에서 package-private로 선언된 메소드는 하위 클래스에서는 package-private보다 높은 접근성인 protected, public을 사용할 수 있고 더 좁은 범위인 private는 사용할 수 없다. 그 이유는, 상위 클래스의 인스턴스는 하위 클래스의 인스턴스로 대체해 사용 가능해야 한다라(리스코프 치환 원칙)때문이다. 이 원칙을 어길 경우 컴파일 오류가 발생한다.
만약, 테스트 목적으로 접근성을 높히고 싶다면, package-private까지는 허용이 된다. 패키지 내부에 테스트 클래스 내부에 작성해서 접근할 수 있기 때문이다. 하지만 그 이상으로 높혀서 공개 API가 되서는 안된다.
public 클래스의 인스턴스 필드는 되도록 public이 아니어야 한다.
모든 인스턴스 필드 낮은 접근성을 유지하는 것이 좋다.
- 필드를 public으로 사용하거나, 가변 객체를 참조하거나, final이 아니면 필드 값을 제한할 수가 없어지며 해당 필드에 대한 모든 것은 불변식을 보장할 수 없게 된다.
- 필드가 수정될 때 막을 수 없기에 public 가변 필드를 갖는 클래스는 일반적으로 스레드 안전하지 않다.
- final이면서 불변 객체를 참조하더라도 내부 구현을 바꾸지 못해 리팩토링을 할 수 없다.
하지만 해당 클래스의 추상 개념을 완성하는데 필요한 구성요소로써의 상수라면 public static final 필드로 공개해도 좋다. 보통 관례적으로 대문자 알파벳으로 작성하며 구분은 밑줄(_)으로 한다. 이런 필드가 가변객체를 참조하면 final이 아닌 필드에서 적용되는 불이익이 모두 적용되기 때문에 반드시 기본 타입 값이나 불변 객체를 참조해야 한다.
자바 9의 모듈 시스템
자바 9에서는 모듈 시스템이라는 개념이 도입되면서 두 가지 암묵적 접근 수준이 추가되었다. 패키지가 클래스의 묶음이듯, 모듈은 패키지들의 묶음이다. 모듈은 자신에 속하는 패키지 중 공개 것들을 선언한다. protected나 public 멤버라도 해당 패키지를 공개하지 않았다면 외부에서 접근 할 수 없다.
하지만, 모듈에 적용되는 접근 수준은 모듈의 JAR 파일을 자신의 모듈 경로가 아니라 애플리케이션 classpath에 두면 그 모듈 안의 모든 패키지는 모듈이 없는 것 처럼 행동하기 때문에 모듈 공개여부와 상관없이 public 클래스가 선언한 모든 public과 protected 멤버를 모듈 밖에서 접근할 수 있게 된다.
또한 모듈을 제대로 사용하기 위해서는 패키지를 모듈 단위로 묶고, 모듈 선언에 패키지들의 모든 의존성 명시해야하고, 소스 트리 재배치 뒤, 모듈 안으로부터 일반 패키지로의 모든 접근에 조치를 취해야 한다.