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

2023. 3. 2. 23:27자바

728x90
반응형

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

정보 은닉각 컴포넌트가 외부에 의존하지 않고 독자적으로 동작할 수 있다는 점 때문에 다음과 같은 장점을 가지고 있다.

 

  • 소프트웨어 재사용성을 높인다.
    • 독립적인 컴포넌트는 다른 환경에서도 사용이 가능하다.
  • 시스템 개발 속도를 높인다.
    • 여러 컴포넌트를 병렬적으로 개발 가능하기 문이다.
  • 시스템 관리 비용을 낮춘다.
    • 디버깅과 컴포넌트 교체 부담이 적기 때문이다.
  • 성능 최적화에 도움을 준다.
    • 병목 현상이 발생하는 컴포넌트를 정해 다른 컴포넌트에 영향을 주지 않고 최적화할 수 있기 때문이다.
  • 큰 시스템 제작 난이도를 낮춰준다.
    • 개별 컴포넌트에 대한 단위 테스트를 해가며 큰 시스템을 개발할 수 있기 때문이다.

자바에서의 정보 은닉

자바에서는 정보 은닉을 위해 다양한 장치를 제공하는데, 이 중 접근 제어 매커니즘은 클래스, 인터페이스, 멤버의 접근성을 명시한다. 이러한 접근 제한자를 제대로 활용하는 것이 정보 은닉의 핵심이 된다.

  • 접근 제한자란?
    • private: 가장 좁은 범위의 접근 제한자로, 멤버를 가지고 있는 클래스에서만 접근 가능하다.
    • package-private: 같은 패키지 내에서만 접근 가능하고 외부에서 접근이 불가능하다.
    • protected: 같은 패키지와 그 클래스를 상속해서 구현하는 자식 클래스에서만 접근이 가능하다.
    • pubilc: 모든 클래스에서 접근이 가능하다.
  • 외부에서 어떤 컴포넌트의 멤버에 접근할 수 있는 권한을 의미하는 것으로 자바에서는 private, package-private(default), protected, public이 있다.

정보 은닉에 있어서 가장 중요한 원칙은 모든 클래스와 멤버의 접근성을 가능한 한 좁혀야한다는 점이다.


클래스에서의 접근 제한자

외부에 노출되어있는 클래스인 톱레벨 클래스와 인터페이스에 부여할 수 있는 접근 수준은 package-private과 public이다.

 

 

엥? 외부 클래스는 당연히 public으로 호출해서 사용하지 않아?


자바에서 클래스나 인터페이스를 구현할 때에는 위의 두 가지 접근 수준만 설정 가능하게 끔한다. 자주 사용하지는 않지만 package-private도 가능하다.

 

클래스 이름 앞에 여러 가지 접근 제어자를 넣어보면 intellij가 사용가능한지 못한지 알려준다.

 

private 사용

 

 

protected 사용

 

package-private 사용

 

public 사용

톱레벨 클래스나 인터페이스를 public으로 선언하면 공개 API가 되며, package-private으로 선언하면 해당 패키지 안에서만 사용할 수 있다. public으로 선언을 해 클래스가 API가 되어버리면 내부 구현을 손쉽게 수정할 수 없게 된다.

 

따라서 public일 필요가 없는 클래스의 접근 수준을 package-private 톱레벨 클래스로 좁히는 것이 중요하다.


멤버에서의 접근 제한자

클래스의 공개 API를 설계한 뒤, 그 외의 모든 멤버는 private으로 만드는 것이 중요하다. 그 이후 같은 패키지의 다른 클래스가 접근해야하는 멤버에 한해 package-private으로 풀어주는 것이 좋다.

private과 package-private 멤버는 모두 해당 클래스의 구현에 해당하기에 공개 API에는 영향을 주지 않는다. 단, Serializable을 구현한 클래스에서는 그 필드들도 의도치 않게 공개 API가 될 수도 있다.

 

 

Serializable을 구현한 클래스…? 그래서 왜 API가 되는데? 무슨 말이야?


  • Serializable을 구현하면 직렬화가 되는 과정에서 클래스를 문자열이나 JSON, CSV 등등의 파일로 저장할 수 있다. 하지만 private으로 구현한 필드들도 함께 직렬화 형태로 바뀌면서 공개되기 때문에 공개 API가 될 수 있다.
  • 저자는 이에 대해 별다른 설명 없이 아이템 86과 87을 참고하라고 하고 있으니 궁금하신 분들은 참고하시면 좋을 것 같습니다!

멤버의 경우에는 package-private에서 protected로 바뀌는 순간 그 멤버에 접근할 수 있는 대상 범위가 엄청 넓어진다. public 클래스의 protected 멤버는 공개 API이므로 영원히 지원되어야한다. 따라서 protected 멤버의 수 또한 적게 유지하는 것이 좋다.


멤버 접근성을 좁히지 못하게 방해하는 제약

 

이는 ‘상위 타입의 객체를 하위 타입의 객체로 치환해도 상위 타입을 사용하는 프로그램은 정상적으로 동작해야 한다.’는 리스코프 치환 원칙을 지키기 위해서도 필요하다. 이를 지키지 않는 경우에는 컴파일 에러가 발생한다.


테스트

테스트를 위해 private 메서드를 package-private으로 풀어주는 것은 괜찮다. 하지만 테스트만을 위해 클래스, 인터페이스, 멤버 등을 공개 API로 만들어서는 안된다. 그러나 테스트 코드를 테스트 대상과 같은 패키지에 두면 package-private 요소에 접근할 수 있기에 위와 같은 공개 API에 대한 고민은 하지 않아도 된다.


public 클래스의 인스턴스 필드는 되도록 public이 아니어야한다.

  • 1. 불변성을 보장하지 못하기 때문여기에, 필드가 수정될 때 다른 작업을 할 수 없게 되기에 public 가변 필드를 갖는 클래스는 일반적으로 스레드 안전하지 않다.
    • (1) 필드가 가변 객체를 참조하거나, (2) final이 아닌 인스턴스 필드를 public으로 선언하면 그 필드에 담을 수 있는 값을 제한하기 어렵게 된다. 이는 필드를 불변하게 만드는 것을 보장할 수 없게 만든다.
  • 2. 인스턴스 필드를 없앨 수 없기 때문
    • 내부 구현을 바꾸고 싶더라도 인스턴스 필드가 공개되어있기에 이를 바꿀 수 없다.

정적 필드에서도 되도록이면 public이 아니기를 권장한다. 다만, 클래스 내의 꼭 필요한 구성 요소로써의 상수라면 public static final 필드로 공개해도 좋다. 관례 상 이러한 경우에는 상수의 이름은 대문자 알파벳으로 쓰고, 각 단어 사이에 _를 넣는다. 이러한 필드는 반드시 기본 타입이나 불변 객체를 참조해야한다.

 

 

상수가 가변 객체를 참조하면 안돼?


  • 가변 객체를 참조한다면 다른 객체는 참조하지 못하지만, 참조된 객체 자체는 수정될 수 있다. 결국 온전히 불변으로써 역할을 하지 못하기 때문에 안된다!

 


길이가 0이 아닌 배열은 모두 변경가능하다. 따라서 클래스에서 public static final 배열 필드를 두거나, 이 필드를 반환하는 접근자 메서드(getter)를 제공해서는 안된다. 이런 필드나 접근자를 제공하면 클라이언트에서 배열의 내용을 수정할 수 있게 된다.

 

// 보안 허점이 있는 코드
public static final Thing[] VALUES = {...};

→ 이를 그대로 반환하는 코드를 작성 시 클라이언트에서 VALUES의 값을 바꿀 수 있다.

 

 

이를 해결할 수 있는 방법은 다음과 같다.

  1. 첫번째 방법은 앞 코드의 public 배열을 private으로 만들고 public 불변 리스트를 추가하는 것이다.
private static final Thing[] PRIVATE_VALUES = {...};
public static final List<THING> VALUES = Collections.unmodifialbeList(Arrays.asList(PRIVATE_VALUES);
  1. 두번쨰 방법은 배열을 private으로 만들고 그 복사본을 반환하는 public 메서드를 추가하는 방법이다.
private static final Thing[] PRIVATE_VALUES = {...};
public static final Thins[] values() {
	return PRIVATE_VALUES.clone();
}

 

그냥 private으로 바꾸면 안돼?


  • 저자는 public으로 이미 구현한 필드를 보호하기 위한 방법에 대해 이야기하고 있다! 그냥 private으로 수정하면 이미 공개된 API가 수정되는 것이기에 이는 치명적인 오류를 발생시킬 수 있다.

자바 9 이후의 모듈 시스템

모듈 시스템의 도입 이후로 두 가지 암묵적인 접근 수준이 추가됐다. 모듈은 패키지들의 묶음을 의미한다. 모듈은 자신에 속하는 패키지 중 공개할 것들을 선언할 수 있다. protected나 public 멤버이더라도 해당 패키지를 공개하지 않았다면 모듈 외부에서는 접근할 수 없다.

 

모듈 시스템을 활용하면 클래스를 외부에 공개하지 않으면서도 같은 모듈을 이루는 패키지 사이에서는 자유롭게 공유할 수 있다.

 

 

하지만 모듈에 적용되는 접근 수준은 주의해서 사용해야한다. 모듈의 JAR 파일을 자신의 모듈 경로가 아닌 애플리케이션 클래스 패스에 두면 그 모듈 안의 모든 패키지는 마치 모듈이 없는 것처럼 행동한다. 즉, 모듈이 공개했는지 여부와 상관 없이 public 클래스가 선언한 모든 public 혹은 protected 멤버를 모듈 밖에서도 접근할 수 있게 된다.

모듈은 여러 면에서 자바 프로그래밍에 영향을 준다. 장점을 누리기 위해서는 다방면의 지식들이 수반되어야하기에 꼭 필요한 경우가 아니라면 당분간은 사용하지 않는 것을 추천한다.


핵심 정리

 


고민해보기

1. 해당 아이템에서 배운 내용과는 다르게 enum 클래스를 제외한 클래스는 대부분 package-private을 사용하지 않는다. 그 이유가 무엇인지 아래의 포스팅을 확인해보고 고민해보자.

 

Java package-private 은 안쓰나요?

 

Java package-private 은 안쓰나요?

 

hyeon9mak.github.io

 

2. 해당 아이템에서 배운 내용 중 어떤 점을 받아들이고, 어떤 점을 받아들이지 말지 고민해보자.

728x90
반응형

'자바' 카테고리의 다른 글

아이템 68, 항상 표준 명명 규칙을 따라야한다.  (0) 2023.03.08
자바의 문자열  (0) 2023.03.05
자바, 객체야~ 일해라!  (0) 2023.02.27
이펙티브 자바, 의존 객체 추입  (0) 2023.02.26
자바, 싱글톤 패턴 실습  (0) 2022.09.03