슬기로운 개발생활

[Java] 상수 인터페이스(Constant Interface)사용에 대한 고찰

by coco3o
반응형

Constant Interface

Constant Interface란 오직 상수만 정의한 인터페이스이다.
interface는 변수를 등록할 때 자동으로 public static final 키워드가 붙는다.
따라서 상수처럼 어디에서나 접근할 수 있으며,
하나의 클래스에서 여러 개의 interface를 implements 할 수 있는데,
Constant Interface를 Implements 할 경우 인터페이스의 클래스 명을 네임스페이스로 붙이지 않고 바로 사용할 수 있다.
이러한 편리성 때문에 상수 인터페이스를 사용한다.

public interface Constants {
    double PI = 3.14159;
    double PLANCK_CONSTANT = 6.62606896e-34;
}

public class Calculations implements Constants {

    public double getReducedPlanckConstant() {
        return PLANCK_CONSTANT / (2 * PI);
    }
}

하지만 많은 사람들이 Constant Interface는 Anti-Pattern이라 하고 있으며, 해당 내용을 살펴보면 다음과 같다.

Constant Interface의 문제점

1. Implements 할 경우 사용하지 않을 수도 있는 상수를 포함하여 모두 가져오기 때문에 계속 가지고 있어야 한다.

2. 컴파일할 때 사용되겠지만, 런타임에는 사용할 용도가 없다. (Marker Interface는 런타임에 사용할 목적이 있으므로 다름)

3. Binary Code Compatibility (이진 호환성)을 필요로 하는 프로그램일 경우, 새로운 라이브러리를 연결하더라도, 상수 인터페이스는 프로그램이 종료되기 전까지 이진 호환성을 보장하기 위해 계속 유지되어야 한다.

4. IDE가 없으면, 상수 인터페이스를 Implements 한 클래스에서는 상수를 사용할 때 네임스페이스를 사용하지 않으므로, 해당 상수의 출처를 쉽게 알 수 없다. 또한 상수 인터페이스를 구현한 클래스의 하위 클래스들의 네임스페이스도 인터페이스의 상수들로 오염된다.

5. 인터페이스를 구현해 클래스를 만든다는 것은, 해당 클래스의 객체로 어떤 일을 할 수 있는지 클라이언트에게 알리는 행위이다. 따라서 상수 인터페이스를 구현한다는 사실은 클라이언트에게는 중요한 정보가 아니다. 다만, 클라이언트들을 혼동시킬 뿐이다.

6. 상수 인터페이스를 Implements 한 클래스에 같은 상수를 가질 경우, 클래스에 정의한 상수가 사용되므로 사용자가 의도한 흐름으로 프로그램이 돌아가지 않을 수 있다.

6번에서 설명하는 내용은 아래와 같다.

public interface Constants {

	public static final int	CONSTANT = 1;
}

public class ExampleClass implements Constants {

	public static final int CONSTANT = 2;	// *

	public static void main(String args[]) throws Exception {
		System.out.println(CONSTANT);
	}
}

[출처 : https://en.wikipedia.org/wiki/Constant_interface]
위와 같이 Constants를 implements 한 ExampleClass에서 CONSTANT 상수를 새로 정의하고 있는데도 경고나 오류 없이 컴파일되며 출력 결과 2를 출력한다.

Constant Interface 대안

안티 패턴을 피하기 위한 대안으로 위키에서는 다음과 같이 사용하도록 안내하고 있다.

public final class Constants { // final로 상속을 막음
    private Constants() {} // private로 객체 생성을 막음

    public static final double PI = 3.14159;
    public static final double PLANCK_CONSTANT = 6.62606896e-34;
}

위와 같이 선언해두고, 사용할 곳에서 import static 구문을 활용하여 상수에 접근한다.
이로써 Constant Interface와 동일한 기능과 편리성을 제공받는다.

import static Constants.PLANCK_CONSTANT;
import static Constants.PI;

public class Calculations {

    public double getReducedPlanckConstant() {
        return PLANCK_CONSTANT / (2 * PI);
    }
}

 

Constant Interface 사용 금지?

상수 인터페이스를 사용할 때 발생하는 문제는 대개 인터페이스를 implements 하여 사용했을 때 나타난다.
즉, 상수 인터페이스를 implements 하지 않고 사용한다면 문제없다.

하지만, "상수 인터페이스를 사용하면 결국 implements 할 수 있는 가능성을 열어 주는거니까 금지해야 하는거 아닌가?" 하는 의문도 든다.

이는 칼을 다룰 줄 모르거나 남용할 가능성이 있다 해서 칼 사용을 금지하는 것과 같다.

실제 프로젝트에서 클래스 파일에 중첩된 상수 인터페이스를 파일 전체에 분산시키지 않고 모든 상수 인터페이스를 그룹화하며 이름 또는 패키지 명을 명확히 지어준다면, 실수라도 implements 하는 일은 없을 거라 생각한다.

인터페이스도 import static 구문을 사용해 인터페이스 명을 네임스페이스로 붙이지 않고 사용할 수 있으며,
다음과 같이 public static final과 같은 키워드를 명시적으로 적지 않아 코드가 줄어든다는 장점도 있다. 이거 외않써?

public interface Constants {
    double PI = 3.14159;
    double PLANCK_CONSTANT = 6.62606896e-34;
}


import static Constants.PLANCK_CONSTANT;
import static Constants.PI;

public class Calculation {

    public double getReducedPlanckConstant() {
        return PLANCK_CONSTANT / (2 * PI);
    }
}

그래서 결론은, 상수 인터페이스를 사용하는 것이 Anti-Pattern이 아니라, 상수 인터페이스를 Implements 하여 사용하는 것이 Anti-Pattern 라는 것이고, implements 하지 않고 사용하면 될 일이다.

 

이 논쟁은 끝나지 않는다.

상수 인터페이스 사용에 대한 많은 의견이 있지만,
결국 많은 사람들이 책에서 인용하고 자신의 입장에 대한 의견과 정당성을 부여하고 있으며, 나에게도 예외는 없다.

인터페이스의 목적은 공통되는 관심 사항을 추상화시키고 이를 각 클래스가 구현하도록 설계하는 것인데,
상수 인터페이스는 클래스에 사용될 상수 값을 제공하는 것이 목적이다.
단지 공통 상수 값을 다른 클래스에서 가져다 쓰기 위해 사용하는 것은 좋지 못한 설계라고 생각하며,
인터페이스는 객체의 동작을 규정할 때 사용하는 것이 바람직하고 상수만을 제공하기 위해 사용하지 않는 것이 맞다고 생각한다.


결정은 개인에게 또는 회사 내 코드 컨벤션에 따라 달라질 수도 있지만, 우리가 할 수 있는 최선은 프로젝트 전체에서 일관성을 유지하는 것이다.

반응형

블로그의 정보

슬기로운 개발생활

coco3o

활동하기