본문 바로가기
카테고리 없음

자바 마커 인터페이스(Marker interface)

by Marco Backman 2024. 2. 18.

마커 인터페이스는 속이 텅 빈 인터페이스를 지칭한다. 그러면 보통 의문이 들 것이다. 아니 내용물 없는 인터페이스를 왜 사용하지?

 

   소프트웨어의 SOLID 법칙 중, L에 해당되는 Liskov Substitution principle을 이용한 기법이다. Liskov Substitution priciple을 이해하려면 SOLID 법칙 중, O에 해당되는 Open-closed principle을 먼저 이해해야 하는 것이 좋지만 이 부분은 다음에 다루도록 하겠다.

 

혹시나 궁금하다면 해당 링크를 읽어보자: https://www.baeldung.com/java-liskov-substitution-principle

 

Liskov Substitution은 상속된 자식 클라스들은 부모 클래스로 오브젝트 선언이 가능하다. 예를 들어 다음과 같이 인터페이스가 있다면 우리는 이 클래스를 부모라 칭한다.

public interface A {
	public void a();
}

 

 

그리고 인터페이스를 따르는 자식 클라스 B가 있다고 하면

public class B implements A {

	@Overrides
	public void a() {
    	System.out.println("hi");
    }
}

 

그러면 다음과 같이 A 타입 클라스 변수에 B 인스턴스 지정이 가능해진다.

class Test {
	A instance = new B();
    
    private void test() {
       instance.a();
    }
}

 

이런 식으로 상속된 자식 클래스 타입으로 자식 인스턴스 지정이 가능 해졌다. 그러면 이게 왜 마커 인터페이스랑 연관된 것이고 왜 유용한가? 이유는 마커 인터페이스가 Liskov Substitution 속성을 창의적으로 이용해서 Type Abstraction이 가능하게 만들기 때문에 코드 중복과 높은 코드 결합력에서 벗어날 수 있게 한다.

 

본인이 했던 실무 경험으로 바탕으로 예시를 들어보겠다.

 

회사가 금융업 관련이라 돈에 관련된 금액과 금융상품의 소유 수량에 매우 민감하다. 안타깝게도 우리는 금융 연산들을 한 곳에서 처리하지 않고 상품 종류와 장소에 따라 다른 외주 프로그램들에 의존하여 서비스를 제공하고 있었다. 문제는 이러한 분산형 처리 방식 때문에 금액이나 상품 수량이 동일하지가 않을 수가 있고, 동일 하지 않은 금액, 가격이나 상품소유량은 빨리 파악해서 금융팀이 수기로 보완하는 프로세싱을 거쳐야 한다. 그러면 동일하지 않은 데이터가 어디서 나오는지, 데이터 오류의 근원이 어디에 있는지 최대한 빨리, 제때 파악을 해줘서 금융팀에게 보고 해줘야 한다.

 

그렇게 해서 나한테 주어진 임무가 교차검증보고서(Reconcilliation report) 제작이다.

각 데이터 베이스, 각 금융 상품들에 따라 데이터를 분류를 하고 비교한 뒤 매칭이 안 되는 데이터들을 보고서에 적고 리포트를 하는 형식이다.

 

문제는 처음 개발을 시작할 때는 Abstraction과 Generics와 같은 상속과 클래스 타입 일반화를 생각하지 않고 만들었었다. 왜냐하면 초기에는 리포트를 하나만 제작했기 때문이다. 그러나 이후에 비슷한 리포트 종류가 지속적으로 추가되면서 느끼게 되었다. 코드가 불필요하게 반복되고 커지고 있다는 것을.. 그래서 본인은 모든 교차 검증 보고서와 관련된 코드를 개편했는데 여기서 쓰인 기법 중 하나가 데코레이터 패턴이다.

 

수많은 데이터 소스와 수 많은 금융상품, 그렇지만 이 보든 것이 비슷한 보고서로 출력이 된다. 만약 이 많은 보고서들을 따로 독립적인 코드로 작성한다면 수많은 클래스, 중복되는 함수들을 사용해야 하고 하나가 추가될 때마다 계속해서 아래와 같은 프로세스를 더해나가야 한다.

 

만약 비 효율적인 코드 작성법으로 각자 다른 데이터베이스 A, B, C에 금융상품 1, 2, 3, 4,... 와 같은 데이터를 교차 검증 해야 한다 가정하면 아래와 같이 프로싱 하게 된다.

 

비 효율 적인 프로세스

 

보시다시피, 교차검증 보고서 종류를 하나를 늘릴 때마다 모든 프로세스를 따로 제작해줘야 했다. 그만큼 코드 중복성이랑 결합도(Coupling)가 높아져서 새로운 클래스 타입의 확장(Scailing)이 어려워졌다.

 

아래 개편된 프로세스를 확인해 보자.

개편된 프로세스

 

이렇게 되면 교차검증보고서 종류가 늘어날 때마다 타입 클래스 하나만 추가하고 나머지는 Abstaction으로 만들어 어떠한 타입도 받게 끔 만든다. 참고로 여기서 추가 된 행은 보고서 형식 오브젝트이다. 매 교차 검증마다 각자의 이름과 값들을 임시 클라스에 저장해주고 보고서를 마지막에 제작해 주는데 이때 엑셀 제작 서비스도 Abstract 클라스 타입을 받고 보고서를 출력하기 때문에 모든 교차 검증 클라스 종류들을 다 받아줘야 한다. 이때 더미 클래스를 Implelent 시켜줘서 이를 가능하게 만드는데 이것을 마커 인터페이스라고 한다.

 

텅 빈 인터페이스를 Implement 하여 상속해서 다른 클래스 타입들을 하나의 타입으로 변환해서 프로세싱을 거치게 만든다.

 

마커 인터페이스는 아래와 같이 생겼다

/**
 * Marker interface for AbstractProcessor
 */
public interface ExcelFieldRecord {
}

 

 

이렇게 해서 엑셀로 출력할 클라스 오브젝트들한테 위의 마커 인터페이스를 implement 시켜 준다.

@NoArgsConstructor
@AllArgsConstructor
@Builder
@Data
public class ReconTypeA implements ExcelFieldRecord {
    String account;
    String description;
}


@NoArgsConstructor
@AllArgsConstructor
@Builder
@Data
public class ReconTypeB implements ExcelFieldRecord {
    String money;
    String description;
}


@NoArgsConstructor
@AllArgsConstructor
@Builder
@Data
public class ReconTypeC implements ExcelFieldRecord {
    String position;
    String description;
}

@NoArgsConstructor
@AllArgsConstructor
@Builder
@Data
public class ReconTypeD implements ExcelFieldRecord {
    String symbol;
    String description;
}

 

아래 코드와 같이 ExcelFieldRecord 타입을 받는 엑셀 제작 서비스(AbstractProcessor)에 위의 모든 클래스들을 넣을 수 있게 되었고 그러면 단 하나의 서비스 함수 호출로도 모든 타입의 교차 검증 클래스를 엑셀로 변환이 가능하게 된다. 아래처럼 말이다.

public abstract class AbstractProcessor<T extends ExcelFieldRecord> {

     public void exportOneRowToExcel(SequenceWriter, T excelFields) thorws IOException {
     	sequenceWriter.write(reconObject);
     }
}

 

이제는 개편된 프로세스 표와 같이 작동하게 되고 결국 정돈되고 프로세스가 획일화되게끔 코드를 작성하였다.