[JAVA] 메서드 간 공통 로직을 함수형 인터페이스를 사용해서 분리하자

종미·2024년 2월 23일
0

☕️ Java

목록 보기
2/5
post-thumbnail

들어가며

보통 중복된 코드는 메서드로 빼서 관리한다. 가독성과 재사용성에 좋다. 그렇다면 아래의 경우라면 어떨까?

    private Cars createCars() {
        try {
            List<String> carNames = inputView.readCarNames();
            return new Cars(carNames);
        } catch (IllegalArgumentException e) {
            outputView.printErrorMsg(e.getMessage());
            return createCars();
        }
    }

    private int readTryCount() {
        try {
            return inputView.readTryCount();
        } catch (IllegalArgumentException e) {
            outputView.printErrorMsg(e.getMessage());
            return readTryCount();
        }
    }

try-cath문, 예외를 처리하는 로직이 중복된다. 해결 방법을 생각해보면

  1. 추상 클래스 혹은 인터페이스를 활용한다.
    : 중복되는 로직을 추상 클래스 메서드로 빼고 추상 클래스를 확장한 하위 클래스에서 개별 로직을 구현한다. (팩토리 패턴)
  2. 함수형 인터페이스를 활용한다.

2번이 더 간단하고 1번의 경우, 여러 클래스로 책임을 분리하는게 애매하고 오버엔지니어링 같다는 생각을 했다. 2번으로 구현하며 함수형 인터페이스를 공부해보았다.

함수형 인터페이스란?

  • 람다식을 다루기 위한 인터페이스로 오직 하나의 추상 메서드만 정의되어 있어야 한다.
  • 함수형 인터페이스로 람다식을 참조할 수 있는 것일 뿐, 람다식의 타입이 함수형 인터페이스의 타입과 일치하는 것은 아니다. 람다식은 익명 객체이고 익명 객체는 타입이 없다. (정확히는 타입이 있지만 컴파일러가 임의로 이름을 정하기 때문에 알 수 없다.)
  • 람다식은 Object 객체로 형변환 할 수 없다. 람다식은 오직 함수형 인터페이스로만 형변환이 가능하다.
  • Java8에서 등장했다. Java Collection Framework에서는 대표적으로 Comparator가 있다. (Comparator는 Java8 이후부터 함수형 인터페이스로 변경되었다.)
@FunctionalInterface
public interface Comparator<T> {
	public int compare(T obj1, T obj2);
}
  • Comparator로 람다식을 다루는 예시를 들어보겠다.
Comparator<Integer> c = (a, b) -> b.compareTo(a);

java.util.function 패키지

java.util.function 패키지에 자주 쓰이는 형식의 메서드가 함수형 인터페이스로 정의되어 있다.

함수형 인터페이스메서드설명
Java.lang.Runnablevoid run()매개변수 X, 반환값 X
Supplier<T>T get()매개변수 X, 반환값 1개
Consumer<T>void accept(T t)매개변수 1개, 반환값 X
Function<T, R>R apply(T t)매개변수 1개, 반환값 1개
Predicate<T>boolean test(T t)매개변수 1개, 반환값 1개(boolean)
BiConsumer<T, U>void accept(T t, U u)매개변수 2개, 반환값 X
BiPredicate<T, U>boolean test(T t, U u)매개변수 2개, 반환값 1개(boolean)
BiFunction<T, U, R>R apply(T t, U u)매개변수 2개, 반환값 1개
UnaryOperator<T>T apply(T t)Function의 변형
BinaryOperator<T>T apply(T t, T t)Function의 변형

함수형 인터페이스를 매개변수로 넘기자

위 예시에서 각 메서드의 개별 로직은

// 1)
		private Cars createCars() {
            List<String> carNames = inputView.readCarNames();
            return new Cars(carNames);
        }
// 2)
		private int readTryCount() {
            return inputView.readTryCount();
        }

이다. 공통 로직을 담은 메서드에 이 부분을 매개변수로 넘기자. 두 메서드 모두 매개변수는 없고 반환값은 1개이므로 Supplier<T>를 매개변수로 사용하면 될 것이다.

    private <T> T retryOnException(final Supplier<T> retryOperation) {
        try {
            return retryOperation.get();
        } catch (IllegalArgumentException e) {
            outputView.printException(e);
            return retryOnException(retryOperation);
        }
    }

retryOnException는 아래와 같이 사용한다.

retryOnException(this::createCars);
retryOnException(this::readTryCount);

소감

우아한테크코스 Level1을 진행하며 페어가 제안한 '함수형 인터페이스'로 메서드의 중복된 로직을 분리하였다. 제네릭 메서드와 함수형 인터페이스의 기초를 공부하면서 헷갈렸던 개념을 바로 잡고, 프로그램의 비즈니스 코드에 직접 적용하면서 재미를 느꼈다. 알아도 막상 적용할 때 생각나지 않을 때도 있는데, 페어 프로그래밍을 하면서 새로운 접근 방식으로 문제를 해결할 수 있었다.

출처

Java의 정석

profile
BE 🪐

0개의 댓글