<4장, 클래스와 인터페이스> [Item 15 클래스와 멤버의 접근 권한을 최소화 하라.]

0
post-thumbnail

정보 은닉(캡슐화)

"잘" 설계된 Component VS "어설프게" 설계된 컴포넌트

이 둘의 결정적 차이는
"(클래스 내부 테이터) && (내부 구현 정보)를 외부 컴포넌트로 부터 얼마나 잘 숨겼느냐" ,즉, 정보 은닉(캡슐화)를 얼마나 잘 했느냐이다.

정보 은닉(캡슐화)는 오직 API를 통해서만 다른 컴포넌트들과 소통하며 서로의 내부 동작에 전혀 관여하지 않는 것이 핵심이다.

Spring에서는 대표적으로 IoC 컨테이너(Inversion of Control)와 DI (Dependency Injection)를 사용하여 컴포넌트들을 관리한다.

장점

정보 은닉의 장점은,
시스템을 구성하는 컴포넌트들을 독립시켜서 개발, 테스트, 최적화, 분석, 수정 등을 개별적으로 할 수 있게하는 것과 연관이 깊다.
다음은 정보은닉의 장점이다.

  1. 시스템 개발 속도를 높인다.

    • 여러 컴포넌트를 병렬로 개발할 수 있기 때문
  2. 시스템 관리 비용을 낮춘다.

  3. 성능 최적화에 도움이 된다. (성능을 높여주지는 않지만)

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

    • 독자적으로 개발된 컴포넌트는 낮선 환경에서도 유용하게 쓰임
  5. 큰 시스템을 제작하는 난이도를 낮춘다.

    • 개별 컴포넌트의 동작이 검증 가능하기 때문

접근 제어 메커니즘 (정보은닉을 위한 장치)

그렇다면 어떻게 정보은닉을 할까.
바로 자바의 접근 제어 메커니즘이 그 예시이다.
각 요소의 접근성은

  1. 해당 요소가 선언된 위치
  2. 접근제한자(private, protected, public)

으로 정해진다.

그중에서도 2. 접근제한자를 제대로 활용하는 것이 정보 은닉의 핵심이라고도 할수 있다!

기본원칙: "모든 클래스와 멤버의 접근성을 가능한 한 좁혀야 한다."

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

톱레벨(가장 바깥) 클래스와 인터페이스에 부여할 수 있는 접근 수준은 package-privatepublic 두가지이다.

<<톱레벨의 의미>>

// MyClass.java (파일명과 클래스명이 동일해야 함)
public class MyClass {
    // 클래스 내용...
}

톱레벨 클래스와 인터페이스를

  1. public으로 선언하면 공개 API
  2. package-private으로 선언하면 패키지 내부에서만 사용할 수 있다.

패키지 외부에 사용할 일이 없다면 가급적 package-private을 권장한다. 이들은 내부 구현이 되어 언제든 수정이 가능하다. 즉, 클라이언트(코드)에 피해 없이 다음 릴리즈에서 수정, 교체, 제거를 할 수 있다.

반면 public으로 선언하면 API가 되므로 하위호환을 위해 영원히 관리해야만 한다.

또다른 중요한 부분은 public일 필요가 없는 클래스의 접근 수준을 package-private 톱레벨 클래스로 좁히는 일이다.

멤버 접근 지정자

다음은 멤버, 즉, ((필드, 메서드, 중첩클래스, 중첩 인터페이스))에 접근할 수 있는 수준 4가지이다.

  1. private: 멤버를 선언한 톱 클래스에서만 사용 가능
  2. package-private: 패키지 내부에서만 접근 가능, 접근 지정자를 명시하지 않을 때 적용됨
  3. protected: package-private + 이 멤버를 선언한 클래스의 하위 클래스에서도 접근 가능
  4. public: 모든 곳에서 접근 가능

접근 지정자 설계 순서

  1. 우선, 클래스의 API를 세심히 설계한 후, 그 외의 모든 멤버를 private로 만든다.
  2. 오직 같은 패키지의 다른 클래스가 접근해야 하는 멤버에 한하여 package-private(default)으로 풀어준다.
  • 단, 권한을 풀어주는 일을 자주하게 된다면 컴포넌트 분해를 고려한다.

  • (예외) Serializable을 구현한 클래스에서는 필드들이 의도치 않게 공개 API가 될 수도 있다. [아이템 86,87]

  • package-private에서 protected로 바꾸는 순간 공개 API가 되므로 protected멤버는 영원히 지원되어야 한다. 따라 내부 동작 방식을 API문서에 적어 사용자에게 공개할 수 있어야 한다. [item 19]
    따라서 protected는 멤버의 수는 적을수록 좋다.

멤버 접근성을 방해하는 제약

한편, 몇가지 제약사항이 있다.

바로, "상위 클래스의 메서드를 재정의 할 때는 그 접근 수준을 상위클래스에서보다 좁게 할 수 없다는 것"이다.

이는 리스코프 치환 원칙(상위클래스의 인스턴스가 하위 클래스의 인스턴스를 대체해 사용) [item 10]을 지키기 위해서 필요하다.

어기면 컴파일 오류가 발생한다.

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

  1. 필드가 가변 객체를 참조
  2. final이 아닌 인스턴스 필드를 public으로 선언

이 두 경우는 그 필드에 제한 할 수 있는 능력이 사라진다. 즉, 불변성 보장이 안 된다.

또한 public 가변 필드를 갖는 클래스는 일반적으로 Thread-safe 하지 않다....
필드가 수정 될 때 다른 작업을 수행하지 못하기 때문이다.

예외는, 해당 클래스가 표현하는 "추상 개념"을 완성하는데 꼭 필요한 구성요소로써의 상수 (보통 단어 사이에 _(밑줄)과 함께 쓰임) 라면 public static final 필드로 공개 가능하다.
말이 어렵지만, 풀어 말하면 하위 클래스에서도 공통적으로 사용하는 상수라는 의미이다..

단, 반드시 기본 타입 값이나 불변 객체를 참조해야 한다. [item 17]
(예: Math 클래스의 수학적인 연산 PI, E 메서드)

class Thing {
    int id;

    public Thing(int id) {
        this.id = id;
    }
}


class NotSafeClass {

    // 클라이언트에서 해당 필드에 참조하여 원본 배열을 변경가능
    public static final Thing[] PRIVATE_VALUES = { new Thing(1), new Thing(2), new Thing(3)};
}


class SafeClass1 {
    
    private static final Thing[] PRIVATE_VALUES = { new Thing(1), new Thing(2), new Thing(3)};

    // public 불변 리스트 반환, 원본 배열에 접근 불가능
    public static final List<Thing> VALUES = Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES));
}


class SafeClass2 {

    private static final Thing[] PRIVATE_VALUES = { new Thing(1), new Thing(2), new Thing(3)};

    // public 복사본 배열 반환(방어적 복사), 원본 배열에 접근 불가능
    public static final Thing[] values() {
        return PRIVATE_VALUES.clone();
    }
}

모듈을 통한 접근 수준

자바 9에서 추가된 모듈 시스템을 통해 두 가지 암묵적 접근 수준(?)이 추가되었다.

즉, module의 공개(export)여부에 따라 접근이 되냐/안되냐 여부가 갈린다

앞서 언급했던 접근지정자는 (private/package-private/protected/public ) 네 가지 수준이라면 모듈접근은 export/non-export 두 가지 수준이다.

++ 모듈은 패키지들의 묶음

  • 자신에 속하는 패키지 중 공개할 것들을 (관례상 module-info.java 파일에) 선언
  • protected/public 멤버라도 해당 패키지를 공개하지 않으면 외부에서 접근 불가능
    - 모듈 안에서는 상관없이 접근 가능
  • 모듈의 jar 파일 경로에 따라 의도치 않은 접근이 일어날 수 있음
profile
끊임없이 질문을 던지고 크게 생각하자.

0개의 댓글