728x90
반응형

본 글은 모던자바인액션 책을 참고하여 작성한 글입니다.

동작 파라미터화란?

👉🏻Java8 등장배경 및 새로운 기능 이전글에서 언급했던 것처럼
동작 파라미터화는 Java8에서 새롭게 등장한 기능입니다.

동작 파라미터화는 말그대로 특정 동작을 파라미터로 입력할 수 있는 기능인데, 이 기능을 활용하면 특정 동작을 정의한 코드를 다른 메서드의 인수로써 넘겨줄 수 있습니다.

why? 왜 사용해야 하는가?

이전글에서 해당 동작 파라미터화 기능이 Java8의 핵심기능인 스트림 API의 토대가 되었다고 언급했었습니다.
스트림 API를 활용하기 위해 사용한다고도 말할 수 있지만, 좀 더 근본적인 사용이유는
"자주 바뀌는 요구사항에 효과적으로 대응"할 수 있기 때문입니다.

자세한건 예시를 보면서 알아보겠습니다.

예시

요구사항 1. 녹색사과 필터링

농장 재고목록 Application에서 활용되는 사과 리스트중 녹색사과만 필터링하는 기능을 추가한다는 요구사항이 있다고 가정해보겠습니다.
해당 요구사항의 기능은 아래 코드로 구현할 수 있습니다.

public static List<Apple> filterGreenApples(List<Apple> inventory) {
    List<Apple> result = new ArrayList<>()
    for (Apple apple : inventory) {
        if ( GREEN.equals(apple.getColor()) ) {
            result.add(apple);
        }
    }
    return result;
}



요구사항 2. 녹색/빨간사과 필터링

농부가 변심하여 빨간 사과도 필터링하고 싶다고 요구사항을 변경하였습니다.
위 코드를 Copy/Paste하여 if문의 조건만 RED로 바꿔 메서드를 추가할 수 있겠지만,
추후에 다양한 색으로 요구사항이 또 들어올 경우에 유연한 대처가 불가능해질 수 있습니다.
또한, 동일한 기능의 코드가 반복될 수 있죠.

이를 보완하기 위해 아래 코드처럼 색이라는 변수를 받을 수 있도록 메서드에 색 파라미터를 추가해줍니다.

public static List<Apple> filterAppleByColor(List<Apple> inventory, Color color) {
    List<Apple> result = new ArrayList<>()
    for (Apple apple : inventory) {
        if ( apple.getColor().equals(color) ) {
            result.add(apple);
        }
    }
    return result;
}



그리고 아래 코드를 호출하여 요구사항을 만족시킬 수 있습니다.

List<Apple> greenApples = filterAppleByColor(inventory, GREEN);
List<Apple> redApples = filterAppleByColor(inventory, RED);



요구사항 3. 색 이외에 무게 기준으로 필터링 가능하도록 기능 추가

농부가 또 변심하여 색 이외에도 가벼운 사과와 무거운 사과로 구분할 수 있도록 해달라고 할 수 있습니다.

public static List<Apple> filterAppleByWeight(List<Apple> inventory, int weight) {
    List<Apple> result = new ArrayList<>()
    for (Apple apple : inventory) {
        if ( apple.getWeight() > weight ) {
            result.add(apple);
        }
    }
    return result;
}



위 코드로도 무게 기준으로 필터링할 수 있도록 기능을 구현할 수 있지만,
해당 무게 필터링 코드는 색 필터링 코드와 대부분 중복됩니다.
이는 소프트웨어 공학의 DRY (Don't Repeat Yourself), 같은 것을 반복하지 말자는 원칙을 어기는 것입니다.

public static List<Apple> filterApple(List<Apple> inventory, Color color, int weight, boolean flag) {
    List<Apple> result = new ArrayList<>()
    for (Apple apple : inventory) {
        if ((flag && apple.getColor().equals(color))
            || (!flag && apple.getWeight() > weight)) {
            result.add(apple);   
        }
    }
    return result;
}



위 코드로 flag에 따라 필터링 대상을 결정하고,
필터링 대상에 맞게 필터링을 수행할 수 있지만, 요구사항 추가에 유연하게 대응할 수 없고,
flag를 다양화해서 어찌어찌 대응한다해도 코드의 복잡성이 굉장히 크게 증가할 것입니다.

이를 대체하는 방안으로 동작 파라미터화기능이 사용될 수 있습니다.

동작 파라미터화 기능의 활용

예시 보완

위 예시에서 동작 파라미터화 기능을 사용하기 위해 아래와 같은 사전준비 작업이 필요합니다.

  1. 필터링 메서드의 참/거짓을 반환하는 인터페이스(프레디케이트) 정의
  2. 참/거짓을 반환하는 인터페이스(프레디케이트)의 구현체 정의 (ex. 150이상 무게 사과 판단, 초록사과 판단 등)
  3. 필터링 메서드의 동작 파라미터화


아래는 프레디케이트 정의 코드입니다.

public interface ApplePredicate {
    boolean test (Apple apple);
}



아래는 프레디케이트의 구현체 코드입니다.

// 150이상 무게 사과 판단
public class AppleHeavyWeightPredicate implements ApplePredicate {
    public boolean test(Apple apple) {
        return apple.getWeight() > 150;
    }
}

// 초록사과 판단
public class AppleGreenColorPredicate implements ApplePredicate {
    public boolean test(Apple apple) {
        return GREEN.equals(apple.getColor());
    }
}



위 처럼 프레디케이트 인터페이스를 정의하고 구현체를 작성하는 패턴을 전략 디자인 패턴이라고 합니다.
전략 디자인 패턴은 전략(알고리즘)을 캡슐화하는 알고리즘 패밀리(프레디케이트 인터페이스)를 정의해둔 다음에 런타임에 상황에 따라 알고리즘을 선택하는 기법입니다. (ApplePredicate : 알고리즘 패밀리, AppleHeavyWeightPredicate/AppleGreenColorPredicate : 전략)

해당 패턴을 활용한다면 filter 메서드에서 파라미터로 받은 동작에 맞게 필터링을 수행할 수 있습니다.
아래 코드는 전략 디자인 패턴 기반의 동작을 파라미터로 받을 수 있도록 수정한 filter 메서드입니다.

public static List<Apple> filterApple(List<Apple> inventory, ApplePredicate p) {
    List<Apple> result = new ArrayList<>()
    for (Apple apple : inventory) {
        if ( p.test(apple) ) {
            result.add(apple);
        }
    }
    return result;
}



위 코드처럼 프레디케이트(인터페이스)를 파라미터로 지정한 다음에 다양한 구현체를 파라미터로 받아 다양하게 필터링을 수행할 수 있도록 하였습니다.
즉, 우리가 전달한 ApplePredicate 객체에 의해 filterApples 메서드의 동작이 결정됩니다.

익명클래스

위와 같은 방법도 충분히 좋지만, 동작을 파라미터화하기 위해 인터페이스를 정의하고 여러 클래스를 정의하는 과정이 상당히 번거롭게 느껴집니다.
이를 간소화하기 위해 익명클래스를 활용하는 방법이 있습니다.

익명클래스를 이용하면 클래스 선언과 인스턴스화를 동시에 할 수 있어 별도로 여러 클래스를 정의하는 과정이 필요하지 않습니다.
아래 코드는 익명클래스를활용하기 이전 filter메소드를 실행하는 코드입니다.

List<Apple> heavyApples = filterApples(inventory, new AppleHeavyWeightPredicate());
List<Apple> greenApples = filterApples(inventory, new AppleGreenColorPredicate());



위 코드에서는 사전에 정의된 클래스를 파라미터로 입력하여 실행하고 있습니다.
아래 코드는 익명클래스를 활용하여 filte메소드를 실행하는 코드입니다.

List<Apple> redApples = filterApples(inventory, new ApplePredicate() {
    public boolean test(Apple a) {
        return RED.equals(a.getColor());
    }
});



익명클래스를 활용하여 별도 클래스를 선언하고 작성하는 과정을 간소화하였지만,
여전히 코드는 불필요하게 공간을 많이 차지하고 있고, 프로그래머가 여전히 익숙하게 느껴질 수 있는 형태의 코드가 아닙니다.

이러한 코드의 장황함은 코드를 구현하고 유지보수하는 데 많은 시간을 소요하게끔 할 수 있습니다.

람다 표현식

위의 익명클래스의 단점을 보완하기 위해 람다 표현식이 사용될 수 있습니다.

List<Apple> result = filterApples(inventory, (Apple apple) -> RED.equals(apple.getColor()));



위 코드를 통해 익명클래스에서의 복잡성을 많이 줄일 수 있습니다.
람다표현식의 자세한 부분은 👉🏻다음글에서 다룰 것입니다.

리스트 형식의 추상화

아래 코드처럼 프레디케이트에 제너릭을 활용한다면,
사과 뿐만 아니라 바나나, 오렌지 등으로 대상까지 다양화할 수 있습니다.

public interface Predicate<T> {
    boolean test(T t);
}

public static <T> List<T> filter(List<T> list, Predicate<T> p) {
    List<T> result = new ArrayList<>();
    for (T e : list) {
        if (p.test(e)) {
            result.add(e);
        }
    }
    return result;
}



실전 예제

실전에서 사용하고 있는 예제로 Comparator, Runnable 등이 있고,
간단히 다뤄보겠습니다.

Comparator로 정렬하기

Java8에서 Collection에는 sort 메서드가 포함되어 있습니다.
해당 sort메서드의 정렬방식을 Comparator를 활용하여 정의할 수 있는데요.

아래 코드처럼 Comparator를 활용하여 정렬 기준을 정의한 후에
sort 메서드에 파라미터로 입력하면 됩니다.

// java.util.Comparator
public interface Comparator<T> {
    int compare(T o1, T o2);
}
  • 익명클래스
inventory.sort(new Comparator<Apple>() {
    public int compare(Apple a1, Apple a2) {
        return a1.getWeight().compareTo(a2.getWeight());
    }
});
  • 람다표현식
inventory.sort((Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()));



Runnable로 코드 블록 실행하기

자바 스레드를 이용하면 코드를 병렬로 실행할 수 있습니다.
Runnable은 스레드에 실행할 동작을 전달할 때, 사용되는 인터페이스입니다.

// java.lang.Runnable
public interface Runnable {
    void run();
}
  • 익명 클래스
Thread t = new Thread(new Runnable() {
    public void run() {
        System.out.println("Hello World!");
    }
});
  • 람다 표현식
Thread t = new Thread(() -> System.out.println("Hello World!"));



이어서

람다표현식에 대해 상세히 알아보겠습니다.

반응형

+ Recent posts