본문 바로가기

이펙티브 자바

[이펙티브 자바] 아이템 15. 클래스와 멤버의 접근 권한을 최소화하라

"클래스와 멤버의 접근권한을 최소화하라"는 원칙은 정보 은닉(Encapsulation)과 관련된 개념으로, 코드를 작성할 때 클래스와 그 내부 멤버에 대한 접근 권한을 가능한 한 제한하는 것을 권장하는 원칙이다. 이렇게 함으로써 코드의 유지 보수성과 안정성을 높일 수 있다.

 

정보은닉의 기본 원칙 : 모든 클래스와 멤버의 접근성을 가능한 한 좁혀야 한다. 소프트웨어가 올바르게 동작하는 한 항상 가장 낮은 접근 수준을 부여해야 한다.

 

멤버에 부여할 수 있는 접근 수준 : 

  1. public: 이 접근 수준을 가진 멤버는 어디서든 접근할 수 있다. 즉, 해당 멤버는 외부 클래스, 패키지, 심지어 다른 모듈에서도 접근할 수 있다.
  2. protected: 이 접근 수준을 가진 멤버는 동일한 패키지 내의 클래스 및 해당 클래스를 상속한 하위 클래스에서 접근할 수 있다. 즉, protected 멤버는 패키지 외부에서는 접근할 수 없지만, 하위 클래스에서는 접근할 수 있다.
  3. default (package-private): 접근 수준을 명시하지 않는 경우, 해당 멤버는 기본(default) 접근 수준을 가진다. 이 경우, 멤버는 동일한 패키지 내의 클래스에서만 접근할 수 있다. 패키지 외부에서는 접근할 수 없다.
  4. private: 이 접근 수준을 가진 멤버는 선언된 클래스 내에서만 접근할 수 있다. 외부 클래스에서는 접근할 수 없다. 이는 정보 은닉을 위해 사용된다.

- public 클래스의 인스턴스 필드는 되도록 public이 아니어야 한다.  클래스의 내부 구현 세부사항을 외부에 노출시키지 않고, 외부에서 직접적으로 접근하여 변경하지 못하도록 하는 것을 의미한다. 가능한한 클래스의 인스턴스 필드는 private으로 선언하고, 필요한 경우에만 접근자(getter)와 설정자(setter)를 제공하여 외부에서 간접적으로 접근하도록 하는 것이 바람직하다. 이를 통해 정보 은닉과 캡슐화를 유지하고, 안정성과 유연성을 높여 데이터 유효성을 보장해준다.

 

- public 가변 필드를 갖는 클래스는 일반적으로 스레드 안전하지 않다. 멀티 스레드 환경에서 해당 클래스의 인스턴스를 여 러 스레드가 동시에 접근하고 수정할 때 문제가 발생할 수 있다. 예를 들어, 한 스레드가 값을 읽는 동안 다른 스레드가 값을 변경할 수 있으며, 이로 인해 데이터 불일치가 발생할 수 있다.

 

- 클래스에서 public static final 배열 필드를 두거나 이 필드를 반환하는 접근자 메서드를 제공해서는 안된다. 

public : 모든 클래스에서 접근 할 수 있다.static :  클래스의 인스턴스화 없이 사용할 수 있다.final : 한 번 초기화되면 그 값이 변경되지 않는다. 즉, 해당 필드는 상수로 취급된다. (상수 : 고정된 값)

아래의 클래스는 public static final 배열 필드를 사용하고, 해당 필드를 반환하는 접근자 메서드를 제공한다. 보안 허점이 숨어있는 코드이다.

public class Constants {
    public static final String[] COLORS = {"Red", "Green", "Blue"};

    public static String[] getColors() {
        return COLORS;
    }
}

 

COLORS 배열을 public static final 필드로 선언하고, getColors() 메서드를 통해 외부에서 이 배열에 접근할 수 있다.

이 코드는 2가지 문제점이 있다.

  1. 불변성 보장의 부족: COLORS 배열은 public static final로 선언되어 있지만, 배열 자체는 불변이 아니다. 즉, 배열의 요소를 변경할 수 있다. 배열은 크기가 고정되고 한 번 생성되면 수정할 수 없는 자료 구조이다. 이러한 배열을 public static final 필드로 제공하면 해당 배열은 수정할 수 없는 상수 배열로 간주된다. 그러나 배열이 참조 타입이기 때문에 해당 배열을 참조하는 다른 변수들이 배열 내의 요소를 변경할 수 있어서 불변성을 보장하지 않는다.
  2. 캡슐화 원칙 위배: 외부에서 COLORS 배열에 직접 접근할 수 있으므로 캡슐화 원칙을 위배한다. 캡슐화는 클래스의 내부 상태를 외부에서 직접 접근할 수 없도록 보호하는 것을 의미한다. 배열의 참조를 변경하는 것은 불가능하지만, 배열 내의 요소를 변경하는 것은 가능하다. 예를 들어, Constants.COLORS[0] = "Yellow"; 같이 배열의 요소를 변경할 수 있다.

아래처럼 코드를 수정하면 해결할 수 있다.

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class Constants {
    // private static final로 선언된 배열 COLORS
    private static final String[] COLORS = {"Red", "Green", "Blue"};

    // getColors 메서드: COLORS 배열을 변경할 수 없는 리스트 형태로 반환
    public static List<String> getColors() {
        // Arrays.asList 메서드를 사용하여 COLORS 배열을 리스트로 변환
        // 이 메서드는 고정 크기의 리스트를 반환하므로 리스트의 크기는 변경될 수 없음
        // unmodifiableList 메서드를 사용하여 리스트를 변경할 수 없는 리스트로 래핑
        return Collections.unmodifiableList(Arrays.asList(COLORS));
    }
}

public 배열을 private 으로 만들고 public 불변 리스트를 추가해주면 된다.

 

결론 : 프로그램 요소의 접근성은 가능한 한 최소한으로 하자. 꼭 필요한 것만 골라 최소한의 public API를 설계하자. 그 외에는 클래스, 인터페이스, 멤버가 의도치 않게 API로 공개 되는 일이 없도록 해야 한다. public 클래스는 상수용 public static final 필드 외에는 어떠한 public 필드도 가져서는 안 된다. public static final 필드가 참조하는 객체가 불변인지 확인하자.