Effective Java : 아이템22. 인터페이스는 타입을 정의하는 용도로만 사용하라.

    핵심 정리

    • 상수를 정의하는 용도로 인터페이스를 사용하지 말 것!
      • 클래스 내부에서 사용할 상수는 내부 구현에 해당한다.
      • 내부 구현을 클래스의 API로 노출하는 행위가 된다.
      • 클라이언트에 혼란을 준다.
    • 상수를 정의하는 방법
      • 특정 클래스나 인터페이스
      • 열거형
      • 인스턴스화 할 수 없는 유틸리티 클래스  (private 생성자등)

    상수를 정의하는 용도로 인터페이스를 사용하는 것은 안티패턴임 

    인터페이스에 상수를 정의하고, 클래스가 이 인터페이스를 구현을 하도록 한다. 그러면 그 클래스는 상수를 바로 사용할 수 있게 된다. 이것은 안티패턴이고, 이유는 두 가지다.

    • 인터페이스의 원래 의도를 오염시킨다.
    • 내부에서 사용되는 상수를 외부로 바로 노출시킨다. (내부 구현 노출)

    아래에서 이 내용들을 하나씩 살펴보고자 한다.

    // 코드 22-1 상수 인터페이스 안티패턴 - 사용금지! (139쪽)
    public interface PhysicalConstants {
    
        static final double AVOGADROS_NUMBER = 6.022_140_857e23;
        static final double BOLTZMANN_CONSTANT = 1.380_648_52e-23;
        static final double ELECTRON_MASS = 9.109_383_56e-31;
    
    }

     

    인터페이스의 원래 의도를 오염시킨다.

    인터페이스의 가장 큰 의도는 '타입을 정의하기 위함'이다. 좀 더 구체적으로 이야기하면, 이 타입은 이런 동작을 해야한다는 것을 정의한다. 하지만 인터페이스에 상수만 정의된 것은 '어떤 행동을 해야하는지' 정의하지 않는다. 따라서 인터페이스의 초기 의도를 벗어나므로 안티패턴이다. 

     

    인터페이스의 내부 구현을 노출시킨다.

    인터페이스에 정의된 일부 상수들은 클래스 내부에서만 사용하기 위한 용도일 수 있다. 이 경우, 상수는 '클래스 내부 정보'에 해당된다. 예를 들어 특정 클래스에서만 이 값을 인터페이스를 구현해서 사용해야 하는데, 의도치 않은 클래스가 인터페이스를 구현해서 상수를 사용할 수 있다. 

    public class MyClass implements PhysicalConstants{
        public static void main(String[] args) {
            // package-private으로 선언된 AVOGADROS_NUMBER에 바로 접근함. → 내부 구현 깨짐
            System.out.println(AVOGADROS_NUMBER);
        }
    }

    이것은 클래스 내부 정보가 인터페이스를 통해서 공개되었기 때문에 캡슐화가 깨지고, 내부 구현이 외부로 노출되어 안티패턴이 된다. 

     

    클라이언트에게 혼란을 줌. 

    클라이언트는 MyClass가 PhtysicalConstants 인터페이스를 구현한 것을 본다. 이 때 클라이언트는 MyClass를 아래처럼 사용해야하는 것으로 오해할 수 있다. 물론 myClass 인스턴스에서 실행할 수 있는 메서드가 아무것도 없어서 더욱 혼란스러워 질 것이다. 

    public class MyClass implements PhysicalConstants{
        public static void main(String[] args) {
            PhysicalConstants myClass = new MyClass();
        }
    }

     


    특정 클래스에서만 사용하는 상수라면?

    만약 특정 클래스에서만 사용하는 상수라면 어떻게 해야할까? 그 상수는 '내부 구현'에 해당되기 때문에 다음 조치를 취해야한다.

    1. 사용하는 클래스로 그 값을 옮긴다.
    2. 그 값을 private / package-private 수준으로 변경해서 외부에 공개하지 않도록 한다. 

     


    상수를 정의하는 방법은? 

    상수를 정의하는 방법은 다음과 같다.

    • 특정 클래스에 넣은 후, 비공개로 사용한다. 
    • enum으로 선언한다. 
    • 인스턴스를 만들 수 없는 상수 유틸리티 클래스에 모아둔다. 

    가장 추천하는 방법은 인스턴스를 만들 수 없는 상수 유틸리티 클래스에 상수값을 모아두는 것이다. 예를 들어 아래와 같이 작성할 수 있다. 

    // 코드 22-2 상수 유틸리티 클래스
    public final class PhysicalConstants {
    
        private PhysicalConstants() {
        }
    
        public static final double AVOGADROS_NUMBER = 6.022_140_857e23;
        public static final double BOLTZMANN_CONSTANT = 1.380_648_52e-23;
        public static final double ELECTRON_MASS = 9.109_383_56e-31;
    
    }

    인스턴스를 만들 수 없도록 수정하고, 상속도 불가능하도록 만들어 오로지 '상수'만 참조해서 사용할 수 있도록 만든다.

    • final 키워드를 붙여 상속 불가능한 메서드로 만든다.
    • private 생성자를 명시해 인스턴스를 생성할 수 없도록 만든다. 

     


    정리

    • 상수를 정의하기 위해서만 인터페이스를 사용하면 안티 패턴이다.
    • 인터페이스의 의도를 해치고, 인터페이스를 구현한 클래스의 내부 구현을 다른 클래스에 노출시킨다. 
    • 상수는 상속 불가능 + 인스턴스 생성 불가능한 상수 유틸리티 클래스를 만들어서 사용한다. 

    댓글

    Designed by JB FACTORY