본문으로 바로가기
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.



잘 설계된 모듈과 그렇지 못한 모듈을 구별 짓는 가장 중요한 속성 하나는 구현 세부사항을 다른 모듈에 잘 감추느냐의 여부다. 잘 설계된 모듈은 구현 세부사항을 전부 API 뒤쪽에 감춘다. 모듈들은 이 API를 통해서만 서로 통신하며, 각자 내부적으로 무슨 짓을 하는지는 신경쓰지 않는다. 이 개념은 정보은닉 또는 캡슐화라고 하며 잘 알려진 설계 원칙이다. 


각 클래스와 멤버는 가능한 한 접근 불가능하도록 만들라.  다시 말해서 개발 중인 소프트웨어의 정상적인 동작을 보증하는 한도 내에서 가장 낮은 접근 권한을 설정하라


● 최상위 레벨 클래스와 인터페이스에 부여할 수 있는 접근 권한은 package-private와 public 두가지이다.


● 최상위 레벨 클래스너 인터페이스는 가능한 package-private로 선언하면 API의 일부가 아니라 구현 세부사항에 속하게 되므로, 다음번 릴리스에 클라이언트 코드를 깨뜨릴 걱정 없이 자유로이 변경, 삭제, 대체할 수 있다.


● 만일 package-private로 선언된 최상위 레벨 클래스나 인터페이스를 사용하는 클래스, 즉 사용자 클래스가 하나뿐이라면, 해당 최상위 레벨 클래스를 사용자 클래스의 private 중첩 클래스로 만들 것을 고려해 보기 바란다. 그러면 패키지 전체가 아니라 단 하나의 클래스만이 해당 클래스의 접근 권한을 가지게 된다.


● 최상위 레벨 package-private 클래스의 접근 권한을 낮추는 것보다 public 클래스의 접근 권한을 낮추는게 더 중요하다. 최상위 package-private 클래스는 이미 구현 세부사항일뿐이지만 public 클래스는 해당 패키지 API의 일부이기 때문이다.


필드나 메서드, 중첩클래스, 중첩 인터페이스 같은 멤버의 접근 권한은 아래의 네 개 중 하나로 설정할 수 있다. 접근 권한이 증가하는 순서로 나열했다.


private - 최사위 레벨 클래스 내부에서만 접근 가능

package-private - 같은 패키지 내의 아무 클래스나 사용할 수 있다. 기본 접근 권한(default)로 알려져 있다.

protected - 선언된 클래스 및 하위 클래스만 사용할 수 있다. 선언된 클래스와 같은 패키지에 있는 클래스에서도 사용 가능하다.

public - 어디서든 다 사용가능


클래스의 public API를 주의 깊게 설계한 뒤에는 반사적으로 다른 모든 멤버는 private로 선언하게 될 것이다. 같은 패키지 내의 다른 클래스가 반드시 사용하는 멤버인 경우에는 private를 제거해서 해당 멤버를 package-private로 만들어야 한다.


● public 클래스의 멤버들의 경우, 접근 권한을 package-private에서 protected로 변경하면 멤버를 사용할 수 있는 범위가 넓어진다. protected로 선언된 멤버는 해당 클래스의 공개 API이며, 영원히 유지해야 한다. 또한 공개된 클래스의 rpotected 멤버는 해당 클래스의 구현 세부사항에 대한 공개된 약속과도 같다. protected 멤버 사용은 자제해야 한다.


● 상위 클래스 메서드를 재정의 할 때 원래 메서드의 접근 권한보다 낮은 권한을 설정할 수 없다. 이 규칙을 어기면 컴파일할 때 오류가 발생한다. 따라서 특정한 인터페이스를 제공할 때는 모든 메서드를 해당 클래스의 public 메서드로 선언해야 한다.


객체 필드는 절대로 public으로 선언하면 안된다. 비-final 필드나 변경 가능 객체에 대한 final 참조 필드를 public으로선언하면, 필드에 저장될 값에 제한할 수 없게 된다. 필드가 반영될 때 특정한 동작이 실행되도록 할 수도 없으므로, 변경 가능 public 필드를 가진 클래스는 다중 스레드에 안전하지않다. 


이 이야기는 static으로 선언된 필드에도 적용되지만 한 가지 예외가 있다. 어떤 상수들이 클래스로 추상화된 결과물의 핵심적 부분을 구성한다고 판단되는 경우, 해당 상수들을 public static final 필드들로 선언하여 공개할 수 있다. 이런 필드들은 관습적으로 대문자로 구성된 이름을 가지며, 이름을 구성하는 단어들은 밑줄 기호로구분한다. 이런 필드들은 반드시 기본 자료형 값들을 갖거나, 변경 불가능 객체를 참조해야 한다. 변경 가능 객체를 참조하는 final 필드는 비-final 필드의 단점들을 그대로 갖는다. 참조 자체는 변경할 수 없지만 참조 대상 객체는 변경할 수 있으므로, 끔직한 결과가 초래된다.


길이가 0 아닌 배열은 언제나 변경 가능하므로, public static final 배열 필드를 두거나, 배열 필드를 반환하는 접근자를 정의하면 안 된다. 그런 멤버를 두면 클라이언트가 배열 내용을 변경할 수 있게 되므로, 보안에 문제가 생긴다.


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



많은 IDE들이 private로 선언된 배열 필드에 대한 참조를 반환하는 접근자를 자동 생성하므로 주의하기 바란다. 그런 코드를 그대로 쓰면 방금 설명한 문제가 생긴다. 이 문제를 고치는 방법은 두 가지다. 첫 번째는 public으로 선언되었던 배열은 private로 바꾸고, 변경이 불가능한 public 리스트를 하나 만드는 것이다.

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



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


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



두 방법 가운데 하나를 택할 때는, 클라이언트가 어떤 작업을 하길 원하는지 따져야 한다. 어떤 자료형으로 반환해야 칼라이언트가 편할 것인가 어느 쪽이 좀 더 나은 성능을 보장할 것인가.


요약하자면 접근 권한은 가능한 낮추라. 최소한의 public API를 설계한 다음, 다른 모든 클래스, 인터페이스, 멤버는 API에서 제외하라. public static final 필드를 제외한 어떤 필드도 public 필드로 선언하지 마라. 그리고 public static final 필드가 참조하는 객체는 변경 불가능 객체로 만들라.