개발 일지

Item15. 클래스와 멤버의 접근 권한을 최소화하라 본문

스터디/Effective Java

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

junjun_ 2023. 1. 4. 17:07
프로그램 요소의 접근성은 가능한 한 최소한으로 하라. 잘 설계된 컴포넌트는 모든 내부 구현을 완벽히 숨겨, 구현과 API를 깔끔히 분리한다.

 

우리가 정보은닉, 혹은 캡슐화라고 하는 이 개념은 소프트웨어 설계의 근간이 되는 원리입니다. 이번 아이템에서는 정보은닉(캡슐화)의 장점과 이러한 정보 은닉을 위해 자바에서 제공해주는 접근 제한 메커니즘에 대해 다룹니다.

 


정보은닉의 장점

시스템 개발 속도를 높인다

  • 여러 컴포넌트 병렬 개발 가능

시스템 관리 비용을 낮춘다

  • 각 컴포넌트를 더 빨리 파악해 디버깅 가능
  • 다른 컴포넌트로 교체하는 비용 하락

성능 최적화에 도움이 된다

  • 완성된 시스템을 프로파일링 해 해당 컴포넌트만 최적화할 수 있다.

소프트웨어 재사용성을 높인다

  • 외부 의존성이 낮은 독자적인 컴포넌트는 낯선 환경에서도 유용하게 쓰일 가능성이 크다.

큰 시스템을 제작하는 난이도를 낮춘다

  • 시스템이 완성되지 않아도 개별 컴포넌트 동작 검증 가능

 

접근 제어 매커니즘

접근제어 메커니즘이란

  • 자바는 정보 은닉을 위해 제공하는 다양한 장치 중 하나
  • 클래스, 인터페이스, 멤버의 접근성(접근 허용 범위)을 명시
  • 각 요소의 접근성은 그 요소가 선언된 위치와 접근 제한자로 정해진다
  • 접근 제한자를 제대로 활용하는 것이 정보 은닉의 핵심이다

접근제어 메커니즘의 기본원칙

소프트웨어가 올바르게 동작하는 한 항상 가장 낮은 접근 수준을 부여해야 한다.

 

클래스에 부여하는 접근 수준

 

  • 톱 레벨 클래스와 인터페이스에는 public과 package-private 만 부여할 수 있다 
    • public으로 선언하면 공개 API가 되어 하위 호환을 위해 영원히 관리해줘야 한다
    • package-private로 선언하면 해당 패키지 안에서만 사용할 수 있다
    • 외부에서 쓸 이유가 없다면 package-private로 선언하여 내부구현으로 만들자
  • 한 클래스에서만 사용하는 package-private 톱레벨 클래스나 인터페이스는 이를 사용하는 클래스 안에 private static으로 중첩시키자
    • 톱레벨로 두면 같은 패키지의 모든 클래스가 접근할 수 있지만  
    • private static을 중첩시키면 바깥 클래스 하나에서만 접근할 수 있다.

 

멤버(필드, 메서드, 중첩 클래스, 중첩 인터페이스)에 부여하는 접근 수준

 

  • public, protected, package-private, private 모두 부여할 수 있다
    • private: 멤버를 선언한 톱레벨 클래스에서만 접근
    • package-private: 멤버가 소속된 패키지 안의 모든 클래스에서 접근
    • protected: package-private의 접근 범위를 포함하며, 이 멤버를 선언한 클래스의 하위 클래스에서도 접근할 수 있다.
    • public: 모든 곳에서 접근할 수 있다.

 

주의할 점

1. 코드를 테스트하려는 목적으로 클래스, 인터페이스 멤버를 공개 API로 만들어서는 안 된다.

 

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

여기에 예외가 있는데, 바로 해당 클래스가 표현하는 추상 개념을 완성하는데 꼭 필요한 상수일 경우에는 public static final로 공개해도 좋다. 다만, 관용적으로 대문자 알파벳과 _의 조합으로 이루어져야 하고 기본 타입 또는 불변 객체를 참조해야 한다. 

 

public 클래스의 필드가 public일 때 문제점

 

  • 변경에 취약하며, 불변식을 보장할 수 없다.
  • 스레드 안전하지 않다.
    • 필드가 수정될 때 다른 작업을 할 수 없음
  • 리팩터링에 취약하다.

 

3. 길이가 0이 아닌 배열은 언제나 변경 가능하므로, public static final 배열 필드를 두거나, 배이 필드를 반환하는 접근자를 정의하면 안 된다.

 

public static final 배열 필드나, 접근자를 제공한다면 클라이언트에서 그 배열의 내용을 수정할 수 있게 된다

다음과 같은 코드에는 허점이 존재한다.

//보안 문제를 초래할 수 있는 코드 
public static final Thing[] VALUES { ... };

 

이 문제를 해결하는 첫 번째 방법은 public 배열을 private로 만들고 변경이 불가능한 public 불변 리스트를 만드는 것이다.

private static final Thing[] PRIVATE_VALUES = { ... };
public static final List<Thing> VALUES =
    Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUE));

 

두 번째 방법은 배열을 private로 선언하고, 해당 배열을 복사하여 반환하는 public 메서드를 추가하는 방법이다. (방어적 복사)

private static final Thing[] PRIVATE_VALUES = { ... };
public static final Thing[] values(){
    return PRIVATE_VALUES.clone();
}

 

핵심정리

프로그램의 접근 제한을 가능한 최소한으로  하라. 꼭 필요한 것만 골라 최소한의 public API를 설계하자
그 외에는 클래스, 인터페이스, 멤버가 의도치 않게 공개되는 일이 없도록 해야 한다.
public 클래스는 상수용 public static final 필드 외에는 어떠한 public 필드도 가져선 안된다. 이때, public static final 필드가 참조하는 객체가 불변인지 확인해야 한다.