2026. 3. 13. 14:22ㆍJAVA
개요
자바 인터페이스 메서드가 정의되어 있고, 몇개의 구현체 클래스가 이미 구현된 상태이고 클라이언트 코드에서 이미 호출되어 있는 상황입니다. 하지만 요구사항의 변경으로 인하여 특정 인터페이스의 메서드 리턴타입을 변경해야 하는 경우에 리팩토링하는 방법에 대해서 소개합니다.
예제 소스코드에 대해서는 현재 개인 프로젝트인 invest72 프로젝트의 소스 코드를 활용합니다.
현재 인터페이스 메서드 상황
투자 금액을 의미하는 InvestmentAmount 인터페이스는 다음과 같습니다.
이러한 상황에서 요구사항의 변경으로 getAmount() 메서드의 리턴타입을 BigDecimal에서 래퍼 클래스인 Money 클래스 타입으로 변경해야 합니다.
public interface InvestmentAmount {
Money calAnnualInterest(InterestRate interestRate);
BigDecimal calMonthlyInterest(InterestRate interestRate);
BigDecimal getAmount();
}
현재 InvestmentAmount 구현체 클래스 상황
- FixedDepositAmount
- MonthlyInstallmentInvestmentAmount
- YearlyInstallmentInvestmentAmount
현재 InvestmentAmount 객체를 이용하여 getAmount 메서드를 호출하는 클라이언트 코드 상황. 현재 28개의 클라이언트에서 객체를 이용하여 getAmount 메서드를 호출하고 있습니다.

문제점
만약 getAmount 메서드의 리턴 타입을 Money로 변경한다면 다음과 같은 문제점이 발생할 것입니다.
- 구현체 클래스의 재정의 메서드에서 컴파일 타임 에러가 발생하여 리턴 타입을 Money로 변경해야 함
- 클라이언트 코드의 getAmount() 메서드의 리턴 타입이 달라져서 클라이언트 코드를 28곳을 각각 수정해야 함
리팩토링
위와 같은 컴파일 타임 에러 및 각각 수정을 하는데 많은 시간을 소비하는 것을 피하기 위하여 리팩토링 기술을 사용하여 메서드의 리턴 타입을 변경하고자 합니다.
default 메서드 추가
우선은 리턴 타입이 Money인 새로운 기본 메서드를 인터페이스에 정의합니다. 자바 문법상 메서드 리턴 타입이 달라도 파라미터 구조가 동일하고, 메서드 이름이 동일하면 컴파일 에러가 발생합니다. 따라서 임시적으로 접미사에 Money를 붙여서 기존 메서드와 다르게 정의합니다.
default 메서드로 정의하였기 때문에 구현체 클래스들에서는 컴파일 에러가 발생하지 않습니다.
public interface InvestmentAmount {
// ...
BigDecimal getAmount();
default Money getAmountMoney() {
throw new UnsupportedOperationException("todo - 금액을 Money로 반환하는 메서드 구현 필요");
}
}
구현체 클래스에서 기본 메서드 재정의
기본 메서드를 인터페이스에 정의하였다면 해당 인터페이스를 구현하는 구현체 클래스에서 getAmountMoney 메서드를 재정의합니다. 다른 구현체 클래스에서도 다음과 같이 비슷하게 getAmountMoney 메서드를 재정의합니다.
package co.invest72.investment.domain.amount;
import java.math.BigDecimal;
import co.invest72.investment.domain.InterestRate;
import co.invest72.investment.domain.LumpSumInvestmentAmount;
import co.invest72.money.domain.Money;
public class FixedDepositAmount implements LumpSumInvestmentAmount {
private final Money amount;
public FixedDepositAmount(BigDecimal amount, String currency) {
this(Money.of(amount, currency));
}
// ...
@Override
public BigDecimal getAmount() {
return getDepositAmount().getValue();
}
@Override
public Money getAmountMoney() {
return amount;
}
}
default 메서드 해제
구현체 클래스에 재정의를 하였다면 기본 메서드로 정의했던 getAmountMoney 메서드의 default 키워드 및 구현 내용을 제거합니다.
public interface InvestmentAmount {
// ...
BigDecimal getAmount();
Money getAmountMoney();
}
제거하고자 하는 기존 메서드에 default 메서드 적용
기존 메서드인 getAmount 메서드에 default 키워드를 적용하여 기본 메서드로 정의합니다.
default BigDecimal getAmount() {
return getAmountMoney().getValue();
}
구현체 클래스의 기존 메서드 제거
구현체 클래스들에서 기존 메서드인 getAmount 메서드를 제거합니다. getAmount 메서드는 default 메서드인 상태이기 때문에 제거하여도 컴파일 에러가 발생하지 않습니다. 이것을 모든 구현체 클래스에서 반복합니다.
public class FixedDepositAmount implements LumpSumInvestmentAmount {
private final Money amount;
public FixedDepositAmount(BigDecimal amount, String currency) {
this(Money.of(amount, currency));
}
// ...
// 코드 제거
//public BigDecimal getAmount() {
// return getDepositAmount().getValue();
//}
@Override
public Money getAmountMoney() {
return amount;
}
}
인라인 메서드 적용
현재 getAmount 메서드를 호출하는 클라이언트 코드는 다음과 같습니다. 메서드를 호출하여 roundToWholeAmount.apply(BigDecimal) 함수에 값을 전달하고 있습니다.
@Override
public BigDecimal getPrincipal(int month) {
return roundToWholeAmount.apply(investmentAmount.getAmount());
}
우리는 위의 코드를 다음과 같이 변경해야 합니다.
@Override
public BigDecimal getPrincipal(int month) {
return roundToWholeAmount.apply(investmentAmount.getAmountMoney().getValue());
}
그런데 위와 같이 getAmount() 메서드를 호출하는 클라이언트 코드는 28곳이나 됩니다. 위와 같은 수정을 일일히 하는 것은 매우 비용이 듭니다. 따라서 일괄적으로 적용하기 위해서 인라인 메서드를 수행합니다.
인터페이스의 getAmount 메서드로 이동한 다음에 인라인 메서드를 수행합니다.
- getAmount 메서드에 커서 올리기
- Refactor -> Inline Method 메뉴 선택
- Refactor 클릭

실행 결과 확인
getAmountMoney 메서드를 선택하고 사용처를 검색해봅니다. 실행 결과를 보면 정상적으로 getAmountMoney 메서드를 호출하고 BigDecimal 매개변수에 전달하기 위해서 추가적인 getValue()를 호출하는 방식으로 성공적으로 변경되었습니다.
물론 BigDecimal 매개변수를 받는 roundToWholeAmount와 같은 함수들도 일괄적으로 변경되면 좋겠지만 이 부분은 별도의 리팩토링 작업을 수행해야 합니다.

빌드 및 테스트 결과 또한 컴파일 에러 없이 테스트를 성공적으로 수행되었습니다.

새로운 메서드 이름을 기존 메서드 이름으로 변경
리팩토링 rename 기능을 이용하여 getAmountMoney 메서드 이름을 기존 메서드 이름인 getAmount 이름으로 변경합니다.
public interface InvestmentAmount {
Money calAnnualInterest(InterestRate interestRate);
BigDecimal calMonthlyInterest(InterestRate interestRate);
Money getAmount();
}
정리
인터페이스의 메서드 리턴 타입 변경 과정
- 변경하고자 하는 새로운 메서드 A 추가 (default 메서드 적용)
- 구현체 클래스에서 새로운 default 메서드 A 구현
- 메서드 A의 default 키워드 제거
- 기존 메서드에 default 키워드 추가
- default 기존 메서드 구현 내용에 새로운 메서드 A를 호출하는 코드로 구현
- 구현체 클래스에서 기존 메서드 구현을 제거
- 기존 메서드를 대상으로 인라인 메서드(Inline Method) 리팩토링 기능을 실행
- Rename 리팩토링 기능을 사용하여 새로운 메서드의 이름을 기존 메서드로 동일하게 변경(getAmountMoney -> getAmount)
'JAVA' 카테고리의 다른 글
| [모던 자바 인 액션] 람다 표현식 (0) | 2023.10.12 |
|---|---|
| JVM(Java Virtual Machine) 실행원리와 구조 (0) | 2023.03.27 |
| 콘솔 입력 객체의 역할에 맞지 않는 기능을 분리하도록 시도하기 (0) | 2023.03.17 |
| JAVA의 JVM, JRE, JDK 정리 (0) | 2021.06.30 |
| JAVA SE, JAVA EE, JAVA ME 차이 (0) | 2021.06.30 |