일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- 자바
- java
- 컴퓨터공학
- 알고리즘
- 깃허브
- 프로그래밍
- 신입사원
- 세마포어
- spring
- 디지털
- CS
- 정보처리기사
- 스프링
- OS
- 공채
- package-private
- Public
- IT
- 신입
- github
- 우리카드
- 스터디
- 운영체제
- 개발
- 이펙티브 자바
- 메모리
- 깃
- 컴퓨터과학
- Effective Java
- 뮤텍스
- Today
- Total
주니어 개발자 성장기
아이템 15. 클래스와 멤버의 접근 권한을 최소화하라. 본문
개요
잘 설계된 컴포넌트는 클래스 내부 데이터와 내부 구현 정보를 외부 컴포넌트로부터 완벽히 숨겨서 구현과 API를 깔끔히 분리한다. 컴포넌트간 통신은 오직 API를 통해서만 이루어지며 내부 동작 방식에는 전혀 관심이 없다. 이것을 정보 은닉 혹은 캡슐화라고 한다. 이것은 소프트웨어 설계의 근간이 되는 원리라고 한다. 그리고 자바에서 캡슐화를 달성하기 위해 필요한 원칙 중 하나가 바로 클래스와 멤버의 접근 권한 최소화이다.
정보 은닉의 장점
- 시스템 개발 속도를 높인다.→ 여러 컴포넌트를 병렬로 개발할 수 있기 때문이다.컴포넌트간에 설계된 인터페이스를 통해서 통신하기 때문에 사용하는 측과 구현하는 측 모두 인터페이스에 맞춰서 개발하면 된다. 이는 각 모듈의 동시 개발을 가능케한다.(팀 단위 개발에서 효율적)
- 시스템 관리 비용을 낮춘다.각 컴포넌트를 더 빨리 파악할 수 있기 때문이다.특히, 인터페이스를 이용한다면 각 컴포넌트가 어떤 역할을 갖고 있는 지 파악하기 쉽다.
- 성능 최적화에 도움을 준다.정보 은닉과 모듈화를 통해서 다른 컴포넌트에 영향을 주지 않고 각 컴포넌트의 장애 지점과 병목 현상을 개선할 수 있기 때문이다.
- 소프트웨어 재사용성을 높인다.독자적인 컴포넌트라면 여러 모듈에서 재사용할 수 있다.
- 시스템 개발 난이도를 낮춘다.
Divide and Conquer
장황하고 어려운 시스템도 나누어서 개발하면 좀 더 쉽게 접근할 수 있다.
원칙
기본 원칙
모든 클래스와 멤버의 접근성을 가능한 한 좁혀야 한다. 달리 말하면, 소프트웨어가 올바로 동작하는 한 항상 가장 낮은 접근 수준을 부여해야 한다는 뜻이다.
- 톱레벨 클래스와 인터페이스의 접근 수준은
package-private
과public
두 가지가 있다.public
public
으로 선언하면 외부에서 접근 가능한 API가 되므로 * ** 하위 호환성을 유지하려면 영원히 관리해야 한다.
*하위 호환성을 지키지 않는다 == API를 바꾸면 해당 API를 사용하는 클라이언트들의 코드를 거기에 맞춰 변형해야 하는 경우
**하위 호환성을 지키는 것에는 장단점이 있으므로 하위 호환성을 깨트리는 코드 변경을 금지시킬 필요는 없다.package-private
패키지 외부에서 쓰지 않을 클래스나 인터페이스는package-private
으로 선언해야 한다.package-private
선언함으로써 API가 아닌 내부 구현으로 만드는 것이 가능하다.→ 그렇다면 인터페이스를 구현하는 클래스를 외부 패키지에서 public하게 접근 가능해야 하는가? 그것은 내부 구현으로package-private
으로 숨기는 것이 좋다고 한다.하지만, 스프링 개발자로서package-private
으로 숨기면Bean
으로 등록 가능한 지 여부가 마음에 걸렸는데 간단한 실험을 해보니 내부 구현인 클래스를package-private
으로 선언해도@Service
,@Component
를 사용한 컴포넌트 스캔이 정상적으로 동작했다. (물론 당연하게도 외부 패키지에서는 내부 구현체만 모를뿐 인터페이스는 반드시 알아야 한다.)
- 한 클래스에서만 사용하는
package-private
클래스나 인터페이스는 이를 사용하는 클래스 안에 *private static
으로 중첩시켜보자.private static
으로 중첩시킨 내부 클래스(inner class
)는 톱레벨 클래스에서만 접근 가능하다. 좀 더 깊은 정보 은닉이 가능해진다. 하지만, 이 보다 중요한 것은 톱레벨 클래스를public
이 아닌package-private
으로 선언하는 것이라고 한다.
*왜 그냥private
이 아닌private static
으로 선언하라고 할까? 그냥private
으로 선언한 내부 클래스는 외부 클래스에 대한 참조를 내부적으로 항상 포함하고 있다. 반면,private static
으로 선언한 클래스는 외부 클래스에 대한 참조를 전혀 갖고 있지 않다. 즉,private
이너 클래스는 톱레벨 클래스의 멤버에 접근할 수 있지만,private static
은 불가능하다. 다시 말해서, 톱레벨 클래스와 독립적으로 사용하기 위해private static
클래스를 사용하는 것이다.
멤버(필드, 메서드, 중첩 클래스, 중첩 인터페이스)에 부여할 수 있는 접근 수준은 네 가지다.
private
: 멤버를 선언한 톱레벨 클래스에서만 접근할 수 있다.package-private
: 멤버가 소속된 패키지 안의 모든 클래스에서 접근할 수 있다. 접근 제한자를 명시하지 않았을 때 적용되는 패키지 접근 수준이다.(단, 인터페이스의 멤버는 기본적으로 public이 적용된다).protected
:package-private
의 접근 범위를 포함하며, 이 멤버를 선언한 클래스의 하위 클래스에서도 접근할 수 있다(제약이 조금 따른다).public
: 모든 곳에서 접근할 수 있다.
멤버의 접근도 최대한 제한하자.
클래스의 공개 API를 세심히 설계한 후, 그 외의 모든 멤버는
private
으로 만들자. 그런 다음 오직 같은 패키지의 다른 클래스가 접근해야 하는 멤버에 한하여 (private
제한자를 제거해)package-private
으로 풀어주자. 권한을 풀어주는 일을 자주하게 된다면 여러분 시스템에서 컴포넌트를 더 분해해야 하는 것은 아닌지 다시 고민해보자.
private
과 package-private
은 내부 구현이라고 이해하면 된다. 따라서 외부에서 사용하는 메서드 혹은 *필드만 public으로 선언하면 된다.
*필드는 public static final
로 선언되는 상수를 제외하고는 public
으로 선언하는 것을 권장하지 않는다. (아이템 16에서 상세히 다룰 것임) 주의할 점은 상수는 배열(혹은 가변 객체)이어서는 안된다. (즉, primitive 타입 혹은 불변 객체여야 한다) 왜냐면 변경이 될 여지가 있기 때문이고 이는 Thread-unsafe하다. 다른 말로 하자면 race condition에 놓인다. (당연히 배열인 상수를 메서드로 제공하는 것도 권장하지 않는다.)
public final
로 선언한다면 어떨까? 여전히 해당 필드는 외부로 공개된 API이기 때문에 해당 필드를 없애는 방법으로는 리팩토링이 불가능하다.
가끔씩 테스트할 때 package-private
으로 필드를 풀어줘야 할 필요가 있는 경우가 있다. 예를 들어서, private
한 필드의 값을 검증하고 싶을 때 Getter가 없다면 해당 클래스 내부의 필드에 값을 검증하고 싶어도 접근이 불가능하기 때문에 시도조차 할 수 없다.
// RestaurantService.class
public class RestaurantService {
private final GroceryService groceryService;
public RestaurantService(GroceryService groceryService) {
this.groceryService = groceryService;
}
}
// RestaurantServiceTest.class
class RestaurantServiceTest {
GroceryService groceryService;
@Test
void test() throws Exception {
RestaurantService restaurantService = new RestaurantService(groceryService);
// groceryService 는 restaurantService 내부의 private field 이다.
// 따라서 테스트 코드에서 접근이 불가능하다.
Assertions.assertNotNull(restaurantService);
Assertions.assertNotNull(restaurantService.groceryService);
}
}
이럴 경우 크게 두 가지 선택지가 있다.
- 기존에 있던 public Getter로 접근한다. (오직 테스트 코드만을 위해 만들지는 말자)
// RestaurantService.class public GroceryService getGroceryService() { return groceryService; }
- 필드를
package-private
으로 선언해 테스트 코드에서 해당 필드를 접근할 수 있도록한다.테스트 코드를 테스트 대상이 같은 패키지에 두면package-private
클래스 혹은 멤버에 접근할 수 있게 되기 때문이다.// RestaurantService.class // private을 package-private으로 전환 final GroceryService groceryService;
- (또 다른 방법) Getter를
package-private
으로 선언해준다.// RestaurantService.class GroceryService getGroceryService() { return groceryService; }
위의 3가지 방법을 이용하면 테스트 코드에서 내부 구현 멤버에 접근이 가능하다.
사실 상태(필드)를 내부(생성자 or 메서드)에서 직접 검증하는 것이 가장 최선의 방법이다. (이 경우에는 생성자에서 필드가 할당이 되기 때문에 생성자에 불변식을 만들었다.)
// RestaurantService.class
public RestaurantService(GroceryService groceryService) {
if(groceryService == null)
throw new IllegalArgumentException("groceryService must not be null");
this.groceryService = groceryService;
}
단지 테스트를 위해서 public
멤버를 만드는 것은 지양하자.
protected
는 공개 범위가 상당히 넓다.public
클래스의protected
멤버는 공개 API이므로 하위 호환성 유지를 원한다면 영원히 관리해야 한다. 또한 내부 동작 방식을 API 문서에 적어 사용자에게 공개해야할 수도 있다.(아이템 19 - 나중에 알아볼 내용이다.) 따라서protected
멤버는 적을수록 좋다.Serializable
Serializable
을 구현한 클래스는 그 필드들(private
,package-private
)도 의도치 않게 공개 API가 될 수도 있다.
이펙티브 자바 완벽 공략 1부 - 완벽 공략 13에 있다고 한다. 기억이 잘 안나는데 Serializable
복습이 필요할 것 같다. 요지는 내부 구현 필드더라도 포맷을 변경하면 역직렬화(Deserialize
)가 불가능하기 때문에 포맷을 유지해야만 한다는 것이다.
- 다른 주의 점은, 오버라이딩할 때는 접근 범위를 좁힐 수 없다는 것이다. 해당 제약이 있는 이유는 리스코프 치환 원칙을 지키기 위해서이다. 이 제약 때문에 캡슐화가 어려워질 수도 있다.
- 자바 9부터는 모듈 시스템의 사용으로 모듈이 다르다면 public과 protected 이어도 외부 모듈에서는 사용할 수 없는 암묵적 접근 수준이 생겼다. 하지만 모듈 시스템을 적극 사용하는 예는 JDK뿐이다. 아직은 직접 모듈 시스템을 사용하는 것은 시기상조라고 한다.
'Java > 이펙티브 자바' 카테고리의 다른 글
아이템 17. 변경 가능성을 최소화하라. (0) | 2023.09.17 |
---|---|
아이템 16. public 클래스에서는 public 필드가 아닌 접근자 메서드를 사용하라. (0) | 2023.09.10 |
아이템 14. Comparable을 구현할지 고려하라 (0) | 2023.09.09 |
방어적 복사, 얕은 복사, 깊은 복사 (0) | 2023.09.02 |
아이템 13. Clone 재정의는 주의해서 진행하라. (0) | 2023.09.02 |