티스토리 뷰

가시성과 컴포넌트 설계

 잘 설계된 컴포넌트는 내부 구현정보를 외부 컴포넌트에 노출하지 않는다. 구현과 API를 깔끔히 분리하는 것이다. 구현정보의 노출은 가시성에 의해 결정된다. 이렇게 내부 구현정보를 프로그램 내부로 숨기는 기법을 정보 은닉 이라고 한다. 정보 은닉은 OOP 의 대표적인 특성으로 개발, 테스트, 최적화, 적용, 분석, 수정에 용이한 프로그래밍을 가능하게 한다. 강점은 다음과 같이 정리할 수 있다.

  • 개발 속도 증가 : 여러 컴포넌트를 병렬로 개발
  • 관리 비용 절감 : 가벼워진 컴포넌트를 분석하기 좋고, 교체의 부담이 적다
  • 성능 최적화 : 최적화 대상 범위가 좁아지기 때문에 다른 컴포넌트에 영향을 주지 않고 시스템 최적화를 시도할 수 있다
  • 재사용성 : 외부 의존도가 낮은 응집도 높은 컴포넌트는 낯선 환경에서도 유용하게 쓰일 가능성이 크다
  • 제작 난이도 감소 : 개별 컴포넌트의 동작을 검증하기 용이하다

자바와 접근 제어 매커니즘

 자바 언어에서 프로그램 요소(클래스, 메서드, 필드)의 접근성은 그 요소가 선언된 위치(패키지) 와 접근 제한자(private, protected, public, package) 로 정해진다. 가시성의 정의는 다음과 같다.

  • public : 모든 곳에서 접근 가능. 인터페이스에 접근 제한자를 명시하지 않았을 때 자동 적용.
  • protected : 멤버를 선언한 클래스의 하위 클래스에서 접근 가능. package-private 접근범위 포함.
  • package-private (default) : 멤버가 소속된 패키지 안의 모든 클래스에서 접근 가능. 클래스에 접근 제한자를 명시하지 않았을 때 자동 적용.
  • private : 멤버를 선언한 클래스에서만 접근 가능.

 

가시성을 고려할 때는 다음 문장부터 시작해보자.

모든 클래스와 멤버의 접근성을 가능한 한 좁혀야 한다.

 

 클래스의 외부에서 접근할 수 있도록 허용하는 접근 지정자는 public, package-private 두 가지이다. public 지정자는 공개 API 를 의미한다. 패키지 외부에서 사용할 일이 없는 요소에 대해서는 package-private 로 선언해보자. 이들은 API 가 아닌 내부 구현이 되어 수정 시의 영향범위가 자신이 속한 패키지 내부로 한정된다. public 선언으로 인한 공개 API는 하위 호환을 위해 영원히 관리해 주어야 한다.

 

 클래스의 공개 API 를 설계한 후, 그 외 모든 멤버는 우선 private 로 선언하자. 그 이후에 타 클래스의 접근이 필요할 때마다 이에 맞추어 권한을 한 단계씩 낮추어 보자. 권한을 풀어주는 일이 반복된다면 컴포넌트의 분해 신호가 될 수도 있다.

접근 제어자 활용

 private, package-private 멤버는 보통 공개 API에 영향을 주지는 않는다. 단, Serializable 인터페이스를 구현한 클래스에서는 그 필드들도 의도치 않게 공개 API가 될 수 있다.

 

 public 클래스의 protected 멤버는 공개 API로 봐야 한다. 이를 확장하는 클래스에서 접근할 수 있기 때문이다. 상위 클래스의 메서드를 재정의 할 때는 그 접근 수준을 좁힐 수 없다. (리스코프 치환 원칙) public 클래스의 인스턴스 필드는 public 으로 선언하지 말자. 필드와 관련된 불변식을 유지할 수 없으며, 스레드에 안전하지도 않다. 상수는 예외가 될 수 있다. 단, public static final 필드로 선언하여 불변하게 유지하자. 관례상 이런 상수의 이름은 언더바(_) 를 포함한 대문자로 명명한다.

 

 테스트 목적으로 접근 범위를 넓히는 경우가 있다. 이렇게 해서는 안된다. 가시성이 낮은 요소들은 가시성이 높은 컴포넌트를 통해 호출될 수 있다. 공개 API 를 통해 테스트해보자. API가 너무 크다면 설계를 분해하자.

 

 배열이나 컬렉션 같이 내부에 자료구조를 관리하는 클래스는 final 로 선언하더라도 요소에 대한 변경을 일으킬 수 있다. 배열에 대해서는 다음과 같이 불변 리스트를 활용하는 방법과 방어적 복사를 구현하는 방법으로 요소변경에 대한 문제를 방지할 수 있다.

 

불변 리스트 활용 : public 배열을 private 로 선언하고, public 불변 리스트를 추가하는 방법

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

 

방어적 복사 : 배열을 private 로 선언하고, 복사본을 반환하는 public 메서드 추가

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

 

자바 9의 모듈 시스템, 자바 17의 sealed class

 객체지향 언어인 자바는 태생상 로직의 묶음을 표현하도록 설계되어 있다. 이 묶음을 어떠한 방식으로 노출할 것이냐는 고민에 따라 가시성의 레벨과 패키지가 생성된다. 시스템이 발전하고 규모가 커지면서, 이 묶음의 단위가 점점 많아진다. 클래스의 수가 점점 많아지면서 이를 정리하기 위한 계층의 깊이가 깊어지고, 패키지도 많아진다. 자바 9에서는 이 정리를 돕기 위해 모듈 시스템 이라는 개념이 도입되었다. 모듈은 패키지들의 묶음이다. 모듈은 자신이 속하는 패키지 중 공개할 것들을 선택해 선언한다. (관례 : module-info.java) 모듈 선언 내의 패키지 요소들은 public, protected 멤버라도 지정되지 않은 외부에서 접근할 수 없다.

 

 자바 15에서는 확장(extends), 구현(implements) 범위에 제약을 가할 수 있는 sealed class 문법을 제공한다. super-calss 에 sealed 키워드를 사용하고 permits 키워드 뒤에 해당 클래스를 확장하거나 구현할 수 있는 클래스를 명시해 제한할 수 있다.

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday