2023. 11. 27. 23:21ㆍBook/이펙티브 자바
어설프게 설계된 컴포넌트와 잘 설게된 컴포넌트의 가장 큰 차이는 바로 클래스 내부 데이터와 내부 구현 정보를 외부 컴포넌트로부터 얼마나 잘 숨겼느냐다.
잘 설계된 컴포넌트는 모든 내부 구현을 완벽히 숨겨, 구현과 API를 깔끔히 분리한다. 오직 API를 통해서만 다른 컴포넌트와 소통하며 서로으 내부 동작 방식에는 전혀 개의치 않는다. => 캡슐화
다음은 정보 은닉의 장점이다.
구현과 API를 분리하는 "은닉화" 의 장점
- 시스템 개발 속도를 높인다.
=> 여러 컴포넌트를 병렬로 개발
- 시스템 관리 비용을 낮춘다.
=> 컴포넌트를 빨리 파악할 수 있음
- 성능 최적화에 도움을 준다.
=> 프로파일링을 통해 최적화할 컴포넌트를 찾고 다른 컴포넌트에 영향을 주지 않으면서 해당 컴포넌트를 개선할 수 있음
- 소프트웨어 재사용성을 높인다.
=> 독자적인 컴포넌트라면
- 시스템 개발 난이도를 낮춘다.
=> 전체를 만들기 전에 개별 컴포넌트를 검증할 수 있기 때문
자바는 정보 은닉을 위한 다양한 장치를 제공한다. 그중 접근 제어 메커니즘은 클래스, 인터페이스, 멤버의 접근성을 명시한다.(public, protected, default, private) 이 접근 제한자를 제대로 활용하는 것이 정보 은닉의 핵심이다.
다음은 기본 원칙이다.
클래스와 인터페이스의 접근 제한자 사용 원칙
- 모든 클래스와 멤버의 접근성을 가능한 한 좁혀야 한다.
- 톱레벨 클래스와 인터페이스에 package-private 또는 public을 쓸 수 있다.
=> public으로 선언하면 API가 되므로 하위 호환성을 유지하려면 영원히 관리해야 한다.
=> 패키지 외부에서 쓰지 않을 클래스나 인터페이스라면 package-private(default)으로 선언한다.(내부 구현체) 그러면 클라이언트에 아무런 피해 없이 다음 릴리스에서 수정, 교체, 제거할 수 있다.
public interface MemberService {
}
=> 인터페이스는 패키지 외부에서 쓰일 일이 있다고 생각해서 접근제한자를 public으로 두었다.
============================================================================================
class DefaultMemberService implements MemberService{
}
=> 구현체는 패키지 내부에서만 쓰이지 외부에서 구현 코드를 알 필요가 없으므로 package-private
으로 두었다.
- 한 클래스에서만 사용하는 package-private 클래스나 인터페이스는 해당 클래스에 private static으로 중첩 시키자.
//구현체는 내부 패키지내에서 있으면 충분하지 않을까?
class DefaultMemberService implements MemberService{
private String name;
//DefalutMemberService 안에서만 쓰이니까 private static으로 중첩시킨다.
private static class PrivateStaticClass {
public static void findById() {
}
//DefaultMemberService와 남남이다.
}
private class PrivateClass {
//내부에 DefalutMemberService를 참조하는 필드가 자동으로 생긴다.
void doPrint() {
System.out.println(name);
}
}
public Member getMember() {
PrivateStaticClass.findById();
return null;
}
}
내부 클래스
- 외부 클래스의 필드 및 메서드에 모두 직접 접근 가능하다.
- 외부 클래스의 인스턴스를 생성해야만 인스턴스 생성이 가능하다. (외부참조: 메모리 누수 가능성)
정적 멤버 클래스
- 외부 클래스의 static 멤버/메서드만 직접 접근이 가능하다.
- 외부 클래스의 인스턴스를 생성할 필요가 없다. (독립적)
- static이 붙어 있지만, 각각 생성한 두 객체는 다른 참조를 갖는다
클래스의 공개 API를 세심히 설계한 후, 그 외의 모든 멤버는 private으로 만들자. 그런 다음 오직 같은 패키지의 다른 클래스가 접근해야 하는 멤버에 한하여 package-private 으로 풀어준다.
권한을 풀어 주는 일을 자주 한다면 컴포넌트를 더 분해해야 하는 것은 아닌지 고민해본다.
클래스와 멤버의 접근 권한을 최소화하라
- private과 package-private은 내부 구현
- public 클래스의 protected와 public은 공개 API => 적을 수록 적다.
- 코드를 테스트 하는 목적으로 private을 package-private으로 풀어주는 것은 허용할 수 있다. 하지만 테스트만을 위해서 멤버를 공개 API로 만들어서는 안된다.(테스트를 같은 패키지에 만든다면 그럴 필요도 없다.)
public class ItemService {
private MemberService memberService;
boolean onSale;
protected int saleRate;
public ItemService(MemberService memberService) {
if(memberService == null) {
throw new IllegalArgumentException("MemberService should not be null.");
}
this.memberService = memberService;
}
//Test를 위해서 getter 메서드를 package-private하게 만드는 건 괜찮다.
MemberService getMemberService() {
return memberService;
}
}
- public 클래스의 인스턴스 필드는 되도록 public이 아니어야 한다.
=> 필드가 가변 객체를 참조하거나, final이 아닌 인스턴스 필드를 public으로 선언하면 그 필드와 관련된 모든 것은 불변 임을 보장할 수가 없다. 또한 필드가 수정될 때 다른 작업(락 획득)을 할 수 없게 되므로 스레드 안전하지 않다. 필드가 fianl이면서 불변이더라도 내부 구현을 바꾸고 싶을 때 public 필드를 없애는 방식으로는 리팩터링 할 수 없다.(외부에 서 참조 할 수도 있기 때문)
- 하지만 해당 클래스가 표현하는 추상 개념을 완성하는 데 곡 필요한 구성요로써 상수라면 괜찮다.
- 클래스에서 public static final 배열 필드를 두거나 이 필드를 반환하는 접근자 메서드를 제공해서는 안된다.
=> 참조가 불변한 것이지 배열 내부 값은 변경 가능하다
=> 해결책 1. 배열을 private으로 만들고 public 불변 리스트 추가
2. 배열을 private으로 만들고 그 복사본을 반환하는 public 메서드 추가
1.첫 번째
private final Thing[] PRIVATE_VALUES = {...};
public static final List<Thing> VALUES =
Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES));
2.두 번째
private final Thing[] PRIVATE_VALUES = {...};
public static final Thing[] values() {
return PRIVATE_VALUES.clone();
}
'Book > 이펙티브 자바' 카테고리의 다른 글
Item17 - 변경 가능성을 최소화 하라(1) (0) | 2023.11.29 |
---|---|
Item16 - public 클래스에서는 public 필드가 아닌 접근자 메서드를 사용하라 (0) | 2023.11.28 |
Item14 - Comparable을 구현할지 고민하라 (1) | 2023.11.25 |
Item13 - clone 재정의는 주의해서 진행하라(2) (1) | 2023.11.24 |
Item13 - clone 재정의는 주의해서 진행하라 (2) | 2023.11.24 |