2장. 동작 파라미터화 코드 전달하기

2장. 동작 파라미터화 코드 전달하기

동작 파라미터화 코드 전달하기 #

쉽게 말하면 메서드를 파라미터로 전달하는 것이다.

동작 파라미터화란? 아직은 어떻게 실행할 것인지 결정하지 않은 코드블록이다. 코드블록은 나중에 프로그램에서 호출한다. 즉, 코드블록의 실행은 나중이다.


예시코드 #

첫 번째 시도 #

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

여기서 조건(GREEN)이 변경(RED)되거나 추가(Weight)된다면 해당 메서드를 사용할 수 없다. (새로운 메서드를 생성할 것이다.) 새로운 메서드를 생성하면 조건 검사 로직 외에는 모두 중복코드이다. 따라서 해당 부분을 파라미터화 하여 개선해볼 수 있다.


두 번째 시도 #

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

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

위의 코드로 ‘첫 번째 시도’ 를 개선해보았다. 위의 코드는 다시 아래와 같이 수정해볼 수 있다.

이는 소프트웨어 공학의 DRY(Don’t Repeat yourself) 같은 것을 반복하지말 것 원칙을 어기는 것이다. 이를 해결하기 위해서 색과 무게를 filter라는 메서드로 합치는 방법도 있다. 따라서 색이나 무게 중 어던 것을 기준으로 필터링할 지 가리키는 플래그를 추가할 수 있다. (하지만 실전에서는 절대 아래의 방법을 사용하지 말아야 한다.)

세 번째 시도 #

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

List<Apple> greenApples = filterApple(apples, "GREEN", 100, true);
List<Apple> heavyApples = filterApple(apples, "GREEN", 100, false);

위와 같은 코드 방식은 절대 사용하지 말라고 권장된다. 과연 true/false 가 의미하는 것이 명확하다고 할 수 있을까라는 것이다. 또, 요구사항이 다시 변경된다면 의미가 없어진다. (예를 들어, 사과의 모양, 원산지 등으로 필터링 조건이 추가된다면..?)


네 번째 시도 (동작 파라미터화) #

public interface ApplePredicate {
    boolean test(Apple apple);
}
public class AppleHeavyWeightPredicate implements ApplePredicate {  // 전략
    public boolean test(Apple appe) {
        return apple.getWeight() > 150;
    }
}

public class AppleGreenColorPredicate implements ApplePredicate {   // 전략
    public boolean test(Apple apple) {
        return "GREEN".equals(apple.getColor());
    }
}

// 조건이 추가/변경될 때, 위와 같은 Predicate 만 생성해주고 적용하면 된다.
public static List<Apple> filterApple(List<Apple> apples, ApplePredicate p) {
    List<Apple> result = new ArrayList<>();
    for(Apple apple : apples) {
        if(p.test(apple)) {
            result.add(apple);
        }
    }
    return result;
}

위와 같은 방식을 전략 패턴이라고 한다. 전략(알고리즘)을 동적으로 선택한다.

전략 디자인 패턴은 각 알고리즘(전략이라 부르는)을 캡슐화 하는 알고리즘 패밀리를 정의해둔 다음에 런타임에 알고리즘을 선택하는 기법이다.

클라이언트는 다양한 전략들 중 한 개를 선택해서 생성하고, 컨텍스트에 주입한다. (사용한다.)


다섯 번째 시도 (동적 파라미터 -> 익명 클래스 적용) #

List<Apple> filteredApples = filterApple(apples, new ApplePredicate() {
    public boolean test(Apple apple) {
        return "GREEN".eqauls(apple.getColor());
    }
});

Predicate 를 생성하기에는 애매할 때, 위와 같이 익명 클래스로 사용할 수 있다.

다만 익명 클래스의 단점은 아직도 코드의 길이가 길다는 것이다. (또, 익숙하지 않다는 것도 있다.)


여섯 번째 시도 (동적 파라미터 -> 익명 클래스 적용) #

List<Apple> filteredApples = filterApple(apples, apple -> "GREEN".eqauls(apple.getColor()));

Predicate 를 생성하기에는 애매할 때, 위와 같이 람다표현식으로 사용할 수 있다. (익명 클래스보다 더 가독성이 뛰어나고 사용하기 쉽다.)

위와 같이 자주 사용되는 Predicate, Function 등을 이미 자바에서 함수형 인터페이스로써 만들어두었다.

List<Apple> filteredApples = apples.filter(apple -> "GREEN".eqauls(apple.getColor()));

사실 위에서 만든 filterApple() 함수를 다른 List, Predicate 형식으로 추상화하면 자바에서 제공하는 filter() 가 된다.

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

Comparator 로 정렬하기 #

java.util.Comparator 객체를 이용하여 sort 에서 동작 파라미터를 사용할 수 있다.

public interface Comparator<T> {
    int compare(T o1, T o2);
}
apples.sort(new Comparator<Apple> {
    public int compare(Apple o1, Apple o2) {
        return o1.getWeight().compareTo(a2.getWeight());
    }
})

이것은 최종적으로 아래와 같이 수정할 수 있다.

apples.sort((Apple o1, Apple o2) -> a1.getWeight().compareTo(a2.getWeight()));