언어/Effective Java

이펙티브 자바 Item15 - 클래스와 멤버의 접근 권한을 최소화하라

now0204 2024. 5. 29. 17:28

잘 설계된 컴포넌트란?

  1. 캡슐화가 얼마나 잘 되었는지.
  2. 노출되는 API와 실제 구현이 얼마나 잘 분리되었는지.
  3. 메시지를 주고받는 두 컴포넌트가 서로의 내부 동작을 신경쓰지 않는지.

 

캡슐화를 잘 지켰을 때 장점

  1. 서로의 구현을 몰라도 되기 때문에 병렬로 개발이 가능하여 개발 속도가 빨라진다.
  2. 잘 분리되어있는 컴포넌트는 관리 포인트가 작다. 디버깅도 빠르고, 교체도 빠르다
  3. 잘 분리되어있는 컴포넌트는 최적화도 그 컴포넌트만 하면 되기 때문에 좋다.
  4. 외부 컴포넌트에 종속되지 않기 때문에 재사용성이 높다.
  5. 전체 시스템이 완성되지 않아도 개별 컴포넌트를 검증할 수 있기 때문에 큰 시스템 개발하는 난이도를 낮춰준다.

1. 캡슐화의 핵심은 "접근제어자"

 

공개 API를 설계하고 그것만 public으로 지정한다 > 나머지는 private으로 만든다. > 구현 중 같은 패키지의 다른클래스가 접근해야한다면 pakage-private로 풀어준다.

 

이 과정에서 너무 자주 푸러주는 일이 발생한다면 컴포넌트를 더 분해해야 하는 것일 수도 있다.

 

+ 추가 고려사항 

 

1. 한 클래스에서만 사용하는 package-private 톱레벨 클래스나 인터페이스는 private 정적 클래스로 중첩시켜본다. 이 중첩 클래스는 포함된 톱레벨 클래스에서만 접근할 수 있다.

2.public 일 필요가 없는 클래스는 pakage-private클래스로 바꾸자 

3. 코드를 테스트하려는 목적으로 접근제한을 풀어주면 안된다. 

  • 테스트 코드를 테스트 대상과 같은 패키기에 두면 package-private 요소까지 접근할 수 있게 된다.

4. 리스코프 치환원칙은 접근 제한을 방해하는 제약으로 작용한다.

  • 상위 클래스의 인스턴스는 하위 클래스의 인스턴스로 언제든 대체해 사용할 수 있어야 한다는 규칙
  • 이를 어길 경우 하위 클래스를 컴파일 할 때 컴파일 에러가 발생한다.
  • 따라서 상위 클래스의 메서드를 재정의할 때는 접근수준을 상위 클래스보다 좁게 설정할 수 없다. 

 

* Top Level 클래스와 인터페이스 접근제한자 : 가장 바깥 의미로 Top Level 클래스나 인터페이스에 부여할 수 있는 접근 수준은 package-private, public 두가지 이다. (package-private은 내부 구현이 되어 언제든 수정이 가능하고, 클라이언트에 피해없이 수정,교체, 제거가 가능하다. 

 

private,package-private가 공개 API가 될 수 있는 "Serializable"구현

public class Member implements Seializable{

	private static final long serialVersionID =1L;
    private String id;
    private String password;
    private String name;
    
    public Member(String id, String password, String name){
    	this.id= id;
        this.password = password;
        this.name = name;
    }
    
}

public class Member implements Serializable{
	private static final long serialVersionID =1L;
    public String id;
    public String password;
    public String name

}
  • private인 Member 클래스를 직렬화한 뒤 -> public Member클래스로 역직렬화하면, private였던 password가 공개 API가 되었다.
  • 공개 API의 제약
    • 공개 API는 영원히 지원되어야 한다. 또 필요에따라 문서화되어야 한다. 그러므로 접근제어는 최대한 좁힐 수 있을 만큼 좁히자.
    • 단, 리스코프 치환 원칙을 위해 하위클래스는 상위클래스보다 좁혀질 수 없다.

2. public 클래스의 인스턴스 필드가 public이 되어서는 안되는 이유 

 

1. 변경에 매우 취약하다.

 

2. 스레드 안전하지 않다.

  • 인스턴스 변수는 힙에 할당되는 공유자원이다. 즉 모든 스레드가 접근할 수 있다.
  • public 인스턴스 변수는 Lock획득 같은 "thread-safe" 부가 작업을 할 수 없기 떄문에 사용하지말자

3. public final 필드의 문제점

  • private 필드라면 내부 구현 변경 시 private 필드를 삭제할 수 있고, Getter든 Setter든 해당 클래스 내부만 수정하면 된다. 하지만 public final 필드의 경우 내부 구현 변경 시 이 필드가 직접 사용되고 있는 모든 소스를 찾아야한다. 
  • 유일하게 허용되는 public static final 필드는 추상 개념을 완성하는데 꼭 필요한 상수이다.
    • 관용적으로 대문자 알파벳과 _조합으로 이루어져 있으며 기본타입 또는 불변객체를 참조해야한다.

4. 유일하게 허용되는 public static final 필드 

  • 추상 개념을 완성하는데 꼭 필요한 상수 (관용적으로 대문자알파벳과_조합으로 이루어져있으며, 기본 타입 혹은 불변객체 참조)

3. public 배열의 문제점 

  • final 키워드를 이용하더라도, 필드 멤버가 배열이라면 배열의 내부 값을 수정할 수 있다.

 

해결방법 

 

1. private 배열과 public 불변리스트 추가 

private static final Integer[] VALUES = {5, 3, 2};
public static final List<Integer> VALUES_LIST = Collections.unmodifiableList(Arrays.asList(VALUES));

 

2. 두 번째 해결방법: private 배열과 복사본 반환하는 public 메서드 추가 (방어적 복사)

private static final Integer[] VALUES = {5, 3, 2};
    
public static final Integer[] values(){
	    return VALUES.clone();
}