Search
Duplicate

아이템 15 - 클래스와 멤버의 접근 권한을 최소화하라

작성자
챕터
4장 - 클래스와 인터페이스
최종 편집
2023/07/16 05:59
생성 시각
2023/07/13 06:16

Item 15. 클래스와 멤버의 접근 권한을 최소화하라

컴포넌트의 잘 설계 되었는가는 내부 구현 정보를 외부 컴포넌트로부터 얼마나 완벽히 숨겼는가로 차이가 난다.
내부 구현을 완벽히 숨겨 구현과 API를 깔끔하게 분리한다면, 내부 동작 방식에는 개의치않고 오직 API를 통해서만 다른 컴포넌트와 소통한다.
캡슐화 혹은 정보 은닉이라 하는 이 개념은 시스템을 구성하는 컴포넌트들을 서로 독립시켜서 개발, 테스트, 최적화, 적용, 분석, 수정을 개별적으로 할 수 있게 해주는 것과 연관이 있다.
여러 컴포넌트를 병렬로 개발할 수 있기 때문에 시스템 개발 속도를 높인다.
각 컴포넌트를 더 빨리 파악하고 컴포넌트를 교체하는데 부담이 적기 때문에 시스템 관리 비용을 낮춘다.
다른 컴포넌트에 영향을 주지 않고 특정 컴포넌트만 최적화 할 수 있기 때문에 성능 최적화에 도움을 준다.
외부에 거의 의존하지 않고 독자적으로 동작하는 컴포넌트는 개발되지 않은 낯선 환경에서 유용하게 쓰일 가능성이 크기 때문에 소프트웨어 재사용성을 높인다.
Java에서 제공하는 정보 은닉 중 하나는 클래스, 인터페이스, 멤버의 접근성(접근허용 범위)을 명시하는 접근 제어 메커니즘이다.
각 요소에 대한 접근 허용 범위는 그 요소가 선언된 위치와 private, protected, public과 같은 접근 제한자로 결정된다.
정보 은닉의 기본 원칙은 모든 클래스와 멤버의 접근 가능성을 가능한 좁혀야 한다는 것이다. 다시말해 올바른 소프트웨어 동작을 하면서 가장 낮은 접근 수준을 부여해야 한다는 뜻이다.
가장 외부에 드러나있는 톱레벨 클래스와 인터페이스에는 package-privatepublic을 부여할 수 있다.
톱레벨 클래스와 인터페이스를 public으로 선안하면 공개 API가 되고, package-private로 선언하면 해당 패키지 안에서만 접근 가능하다.
그러한 이유로 패키지 외부에서 사용할 일이 없다면 package-private로 선안하는 것이 권장된다. public으로 선언하게 되면 하위 호환을 위해 지속적으로 관리해줄 필요가 발생할 수 있다.
하나의 클래스에서만 사용되는 package-private 톱레벨 클래스나 인터페이스가 있다면, 해당 클래스를 사용하는 클래스 안에 private static으로 넣어서 사용하는 것을 권장한다.
package-private class A { ... } package-private class B { A getAFromB(); }
Java
복사
package-private class B { private static class A { ... } A getAFromB(); }
Java
복사
첫 번째 코드처럼 톱레벨로 두면 같은 패키지의 모든 클래스가 접근 가능하지만, private static으로 중첩시키면 바깥 클래스 하나에서만 접근할 수 있다.
public 클래스는 그 패키지의 API이지만 package-private 톱레벨 클래스는 내부 구현에 속하기 때문에, public일 필요가 없는 클래스의 접근 수준을 package-private 톱레벨 클래스로 좁히는 것이 권장된다.
필드, 메서드, 중첩 클래스, 중첩 인터페이스와 같은 멤버에 부여할 수 있는 접근 수준은 4가지가 있다.
private : 멤버를 선언한 톱레벨 클래스에서만 접근 가능
package-private : 멤버가 소속된 패키지 안의 모든 클래서에서 접근 가능
접근 제한자를 명시하지 않으면 인터페이스는 public, 인터페이스를 제외하면 기본적으로 package-private가 적용된다.
protected : package-private의 범위를 포함하여 이 멤버를 선언한 클래스의 하위 클래스에서도 접근 가능
public : 모든 곳에서 접근 가능
클래스에서 같은 패키지의 다른 클래스가 접근해야 하는 멤버에 한해 package-private로 풀어주고 그 외의 모든 멤버를 private로 만들자
권한을 풀어주는 일이 너무 잦다면 시스템에서 컴포넌트를 더 분해해야 하는게 아닌지 생각해보자
private와 package-private 멤버는 모두 클래스의 내부 구현에 해당하므로, 일반적으로 공개 API에 영향을 주지 않는다.
다만 Serializable을 구현한 클래스에서는 그 필드들도 의도치 않게 공개 API가 될 수도 있다.
Serializable 직렬화(serialize)란 자바 언어에서 사용되는 Object 또는 Data를 다른 컴퓨터의 자바 시스템에서도 사용 할수 있도록 바이트 스트림(stream of bytes) 형태로 연속전인(serial) 데이터로 변환하는 포맷 변환 기술을 일컫는다. 그 반대 개념인 역직렬화는(Deserialize)는 바이트로 변환된 데이터를 원래대로 자바 시스템의 Object 또는 Data로 변환하는 기술이다.
public 클래스의 멤버가 package-private에서 protected로 바뀌면, protected는 공개 API이므로 해당 멤버에 접근할 수 있는 범위가 매우 넓어지기 때문에 protected 멤버의 수는 적을수록 좋다.
상위 클래스의 메서드를 재정의할 때는 리스코프 치환 원칙을 지키기 위해 접근 수준을 상위 클래스에서보다 좁게 설정할 수 없다.
리스코프 치환 원칙(LSP, Liskov Substitution Principle) 서브 타입은 언제나 기반 타입으로 변경가능해야 한다는 원칙이다. 다시말해 부모 클래스는 자식 클래스 인스턴스로 대체 될 수 있어야하고, 자식 클래스는 최소한 자신의 부모 클래스에서 가능한 행위는 수행이 보장되어야 한다는 의미이다.
단지 코드를 테스트하려는 목적으로 인해 클래스나 인터페이스, 멤버를 공개 API로 만들어서는 안된다.
public 클래스의 private 멤버를 package-private까지 풀어주는 것은 허용할 수 있지만, 그보다 더 풀어주게 되면 공개 API가 되므로 해서는 안된다.
테스트 코드를 테스트 대상과 같은 패키지에 두면 package-private 요소에 접근할 수 있기 때문에 할 이유도 없다.
public 클래스의 인스턴스 필드는 가급적 public이 아니어야한다.
필드가 가변 객체를 참조하거나 final이 아닌 인스턴스 필드를 public으로 선언하게 되면 해당 필드에 담을 수 있는 값을 제한할 수 없게 된다. 다시말해 해당 필드와 관련된 불변식을 보장할 수 없게 된다는 뜻이다.
또한 필드가 수정되는 동안 다른 작업을 할 수 없게 되므로 public 가변 필드를 갖는 클래스는 일반적으로 스레드 안전하지 않다.
final이면서 불변 객체를 참조하더라도 public으로 구현하게되면, 내부 구현을 때 public 필드를 없애는 방식으로는 리팩토링할 수 없게 된다.
정적 필드에서 해당 클래스가 표현하는 추상 개념을 완성하는데 꼭 필요한 구성요소로써의 상수라면 public static final 필드로 공개해도 된다.
public static final 필드는 반드시 기본 타입 값이나 불변 객체를 참조해야 한다.
public static final로 두어도 길이가 0이 아닌 배열은 변경이 가능하므로, 배열을 필드로 두거나 이 필드를 반환하는 접근자 메서드를 제공하면 안된다.
private static final Thing[] PRIVATE_VALUES = { ... }; // 첫 번째 방법 public static final List<Thing> VALUES = Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES)); // 두 번째 방법 public static final Thing[] values() { return PRIVATE_VALUES.clone(); }
Java
복사
이와 같이 배열을 private로 만들고 public 불변 리스트를 추가하거나, private 배열의 복사본을 반환하는 public 메서드를 추가하는 방어적 복사 방법으로 해결할 수 있다.
자바 9에서는 모듈 시스템 개념이 도입되어 두 가지 암묵적 접근 수준이 추가되었다.
패키지가 클래스들의 묶음이라면 모듈은 패키지들의 묶음으로, 모듈은 자신에 속하는 패키지 중 공개(export)할 것들을 선언할 수 있다.
protected나 public 멤버라도 해당 패키지를 공개하지 않는다면 모듈 외부에서는 접근할 수 없다. 모듈 내부에서는 공개 선언 여부는 아무런 영향을 받지 않는다.
모듈 시스템을 활용하면 클래스를 외부에 공개하지 않으면서도 모듈 내 패키지 사이에서 자유롭게 공유할 수 있다.
생성한 모듈의 jar 파일을 자신의 모듈 경로가 아닌 어플리케이션의 클래스패스(classpath)에 두면, 모듈 안의 모든 패키지는 모듈의 공개 여부와 상관 없이 public과 protected 멤버를 모듈 밖에서도 접근할 수 있게하여 모듈이 없는 것처럼 동작하기 때문에 조심해서 사용해야 한다.
이러한 암묵적 접근 수준을 적극 활용한 예시가 JDK 자체로, 자바 라이브러리에서 공개하지 않은 패키지들은 해당 모듈 밖에서 절대 접근할 수 없다.
JDK 외에도 모듈 개념이 받아들여질 지를 예측하기 이른 감이 있으니 당분간은 꼭 필요한 경우가 아니라면 사용하지 않는게 좋을 것 같다.