본문으로 바로가기

1. 캡슐화

컴포넌트는 모든 내부 구현을 숨김으로써 구현과 API를 분리한다. 오직 API를 통해 다른 컴포넌트와 소통할 뿐, 서로의 내부 동작 방식은 신경쓰지 않는다.

 

컴포넌트들을 독립시키는 캡슐화의 장점은 다음과 같다.

 

  • 시스템 개발 속도를 높인다.
    • 여러 컴포넌트를 병렬로 개발할 수 있다.
  • 시스템 관리 비용을 낮춘다.
    • 각 컴포넌트를 빨리 파악하여 디버깅이 쉽고, 컴포넌트 교체 부담이 적다.
  • 성능 최적화에 기여한다.
    • 다른 컴포넌트에 영향을 주지 않고 특정 컴포넌트를 최적화할 수 있다.
  • 소프트웨어 재사용성을 높인다.
    • 독자적으로 동작하는 컴포넌트는 낯선 환경에서도 유용하게 쓰일 가능성이 높다.
  • 큰 시스템을 제작하는 난이도를 낮춘다.
    • 시스템 전체가 완성되지 않아도 개별 컴포넌트의 동작을 테스트할 수 있다.

 

2. 접근 제한자

모든 클래스와 멤버의 접근성을 가능한 좁혀야 한다. 멤버 클래스가 아닌 일반 클래스는 아래의 접근 제한자들 중 public과 package-private(default)만 사용 가능하다.

 

  • private : 멤버를 선언한 톱 레벨 클래스에서만 접근할 수 있다.
  • package-private(default) : 멤버가 소속된 패키지 안의 모든 클래스에서 접근할 수 있다.
    • 접근 제한자를 명시하지 않을 때 자동 적용되지만, 인터페이스의 멤버는 기본적으로 public이다.
  • protected : package-private 접근 범위를 포함하며, 해당 멤버를 선언한 클래스를 상속한 다른 패키지의 하위 클래스에서도 접근이 가능하다.
  • public : 모든 패키지에서 접근 가능하다.

2.1. API

톱 레벨 클래스나 인터페이스를 public으로 공개하면 API가 되기 때문에 하위 호환을 위해 계속 관리해야 한다. package-private으로 선언하면 API가 아닌 내부 구현이 되어 언제든 수정할 수 있기 때문에, 외부 패키지에 공개할 필요가 없다면 package-private을 사용한다.

2.2. Inner Class

한 클래스에서만 사용하는 package-private 톱 레벨 클래스 혹은 인터페이스는 사용하는 클래스 내부에 private static Inner Class로 중첩할 수 있다. Outer Class를 제외하면 해당 클래스에 접근할 수 없게 된다.

2.3. 멤버

클래스의 공개 API를 설계하고 그 외의 모든 멤버는 private으로 선언한다. 패키지의 다른 클래스가 접근해야 하는 멤버에 한해 package-private으로 풀어준다. protected는 공개 API로서 영원히 지원되어야 하고 문서에 공개해야 할 수도 있기 때문에 적을 수록 좋다.

 

3. 제약

상위 클래스의 메서드를 오버라이드할 때 접근 수준을 상위 클래스보다 더 좁게 설정할 수 없다. 이는 상위 클래스의 인스턴스는 하위 클래스의 인스턴스로 대체될 수 있다는 리스코프 치환 원칙을 지키기 위함이다.

 

4. public 관련 고려사항

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

  • final이 아닌 필드 및 final이더라도 가변 객체를 참조하는 필드 등이 public이면 외부에서 수정이 가능하기 때문이다.
    • 이는 필드와 관련된 불변식이 깨지고, 스레드에도 안전하지 못하다.

다만 해당 클래스가 표현하는 추상 개념을 완성하는데 꼭 필요한 구성 요소로써의 상수는 public static final로 공개해도 좋다. 이런 필드는 반드시 불변 객체를 참조해야 한다. 다른 객체를 참조하지 못하더라도, 참조하는 객체 자체가 변경될 수 있기 때문이다.

Test.java

public static final Thing[] PRIVATE_VALUES = { ... };

 

배열은 final이더라도 수정이 가능하다. 따라서 해당 필드에 직접 접근할 수 있는 API를 공개하면 안 된다.

 

Test.java

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();
}

 

외부에서 변경을 가할 수 있는 필드는 불변 컬렉션으로 제공하거나 방어적 복사본을 제공하는 API를 사용하도록 한다.

 

5. 모듈

Java 9 부터는 모듈의 개념이 도입되었다. 모듈이란 패키지의 묶음으로서 자신에 속한 패키지 중 공개할 것을 지정한다.

 

  • protected 혹은 public 멤버라도 해당 패키지를 공개하지 않았다면 모듈 외부에서 접근할 수 없다.
    • protected와 public의 효력이 모듈 내부로 제한된다.
  • JAR 파일을 자신의 모듈 경로가 아닌 클래스패스에 위치시키면 모듈의 효과가 사라진다.
    • 패키지 공개 여부에 상관없이, 모듈 외부에서 모듈 내부 패키지의 public 및 protected 멤버에 접근 가능해진다.

요약

프로그램 요소의 접근성은 가능한 한 최소한으로 하라. 꼭 필요한 것만 골라 최소한의 public API를 설계하자.

 

그 외에는 클래스, 인터페이스, 멤버가 의도치 않게 API로 공개 되는일이 없도록 해야 한다. 

 

public static final 필드 외에는 어떠한 public 필드도 가져서는 안된다.

 

public static final 필드가 참조하는 객체가 불변인지 확인하라.