본문 바로가기

STUDY/이펙티브자바

4-1) 클래스와 멤버의 접근 권한을 최소화하라

캡슐화(정보 은닉)

 

잘 만들어진 컴포넌트의 특징 중 하나는 내부 구현 내용을 외부에 잘 숨긴다는 것이다. 외부의 사용자는 내부의 구현에 신경쓰지 않고 컴포넌트를 사용한다. 이는 OOP와 소프트웨어 설계의 기본이 캡슐화(정보 은닉)와 같은 뜻이다. 캡슐화는 내부의 상태나 정보를 외부에서 접근할수 없도록 하여 감추는 것인데, 이렇게 하면 여러 장점을 얻을 수 있다.

  1. 시스템 개발 속도를 높인다 > 여러 컴포넌트를 병렬로 개발 할 수 있다.

  2. 시스템 관리 비용을 낮춘다 > 디버깅 하기 쉽고, 컴포넌트 교체 부담도 적다.

  3. 성능 최적화에 좋다 > 내부 컴포넌트의 코드 수정이 외부 컴포넌트에 주는 영향이 적기 때문에 성능최적화 하기에 좋다. 

  4. 소프트웨어 재사용성을 높인다 > 캡슐화(정보은닉)을 지킨 컴포넌트는 비교적 독릭적으로 동작하기 때문에, 다른 시스템에서 재사용하기에 좋다.

  5. 큰 시스템을 제작하는 난이도를 낮추어 준다 > 시스템 전체가 완성되지 않은 상태에서도 개발 테스트가 가능하다.

 

 


접근 제한자

 

public class Counter {
    public Integer cnt;

    public void counter() {
        this.cnt = 0;
    }
    
    public void count() {
        this.cnt++;
    }
    ...
}

// counter이 의도하지 않은 방식으로 동작한다.
Counter counter = new Counter();
counter.cnt = -1;

정보 은닉을 위한 기본 원칙은 매우 간단하다. 모든 클래스와 멤버의 접근성을 가능한 좁혀야 한다. Java에서 멤버에 부여할 수 있는 접근수준은 아래와 같다.

  1. private: 멤버를 선언한 클래스에서만 접근할 수 있다.

  2. package-private: 멤버가 소속된 패키지 안의 클래스에서만 접근할 수 있다. (클래스의 기본값)

  3. protected: 멤버가 소속된 패키지 안의 클래스와 클래스를 상속한 하위 클래스에서만 접근할 수 있다.

  4. public: 모든곳에서 접근할 수 있다. (인터페이스 기본값)

 

 


필드에 public 사용은 가급적 피하라

 

public class Counter {
    public Integer cnt;
    ...
}

 

public class Counter {
    private Integer count;
    ...
    public void count() {
        synchronized(this) {
            this.count = 0;
        }
    }
    ...
}

클래스의 인스턴스 필드에 public 사용은 피해야한다. 인스턴스 필드에 public을 사용하면 외부에서 값을 변경하여 의도치 않은 동작을 유발할 수 있다. 그리고 멀티 쓰레드 환경에서 사용되는 클래스라면 thread-safe하지 않게된다. 필드를 private로 바꾸고 정해진 public 메소드에서만 값을 컨트롤 해야 의도한 동작을 수행하고, thread-safe를 보장할 수 있다.

 

 


배열은 final 이어도 값이 변경될 수있다.

 

public class Alphabet {
    public static final char[] CHARS = {'a', 'b', 'c'};
}

// CHARS 필드의 변경을 막기위해 final로 선언하였으나, 배열 내부의 값이 바뀐다.
System.out.println(Alphabet.CHARS);
Alphabet.CHARS[1] = 'a';
Alphabet.CHARS[2] = 'a';
System.out.println(Alphabet.CHARS);

 

final로 선언한 필드는 그 값이 바꿀 수 없다. 하지만 위의 코드에서 final로 선언한 CHARS의 값이 바뀌는 것을 볼 수 있다. 왜일까? Alphabet 클래스의 CHARS 필드에는 배열의 값들이 저장되어 있는 것이 아니라, 배열의 값들이 저장되어있는 메모리를 바라볼 수 있는 참조값이 저장된다. 그리고 final은 CHARS에 저장된 이 참조값을 변경하는 것만 막아준다. 따라서 Alphabet. CHARS = new char[10]; 과 같은 코드는 컴파일 에러를 발생시키지만, Alphabet. CHARS[1] = 'a'; 는 CHARS의 참조값이 가르키는 배열 공간을 수정하는 것이기 때문에 막지 못한다. 배열의 값 수정까지 막기위해서는 아래 코드처럼 수정 불가능한 리스트를 만들어야 한다.

 

public class Alphabet {
    private static final Character[] ORIGIN_CHARS = {'a', 'b', 'c'};
    public static final List<Character> CHARS = Collections.unmodifiableList(Arrays.asList(ORIGIN_CHARS));
}

 

 


Java9의 모듈

 

// 모듈 내부의 abc.test 패키지를 외부에 공개한다.
module abcmodule {
    exports abc.test;
}

Java9에서는 모듈이라는 개념이 추가되었다. 모듈 내부에 만들어진 컴포넌트들에 대해서 어떤것을 외부에 공개할지 선언해주야한다. 즉 public 클래스라도 모듈에서 해당 클래스를 외부에 공개하지 않으면 사용할 수 없다.

728x90