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

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

  • 동작 파라미터화를 이용하면 자주 바뀌는 요구사항에 효과적으로 대응할 수 있다.
  • 동작이란 어떻게 실행할 것인지 결정하지 않은 코드 블록이다.
  • 메서드의 인자로 코드블럭(메서드)을 전달하는 패턴

2.1 변화하는 요구사항

  • 농장 재고목록 리스트에서 녹색 사과만 필터링 하는 기능을 추가한다고 가정하자.

2.1.1 녹색 사과 필터링

public enum Color {
    RED, GREEN
}

public class Apple {

    Color color;
    int weight;
    // Constructor, Getter, Setter 생략
}
public class AppleFilter {
    public static List<Apple> filterGreenApples(List<Apple> inventory) {
        List<Apple> result = new ArrayList<>();
        for (Apple apple : inventory) {
            if (Color.GREEN.equals(apple.getColor())) {
                result.add(apple);
            }
        }
        return result;
    }
}
  • 빨간 사과도 필터링 해달라는 요구사항이 추가된다면? 다른 색상이 추가될 가능성이 있다면?

2.1.2 색을 파라미터화

public class AppleFilter {
    public static List<Apple> filterApplesByColor(List<Apple> inventory, Color color) {
        List<Apple> result = new ArrayList<>();
        for (Apple apple : inventory) {
            if (color.equals(apple.getColor())) {
                result.add(apple);
            }
        }
        return result;
    }
}
  • 사과의 무게로 필터링 하려면?
public class AppleFilter {
    public static List<Apple> filterApplesByWeight(List<Apple> inventory, int weight) {
        List<Apple> result = new ArrayList<>();
        for (Apple apple : inventory) {
            if (apple.getWeight() > weight) {
                result.add(apple);
            }
        }
        return result;
    }
}
  • 무게 필터링 코드와 색 필터링 코드에서 중복되는 코드를 제거하려면?

2.1.3 가능한 모든 속성으로 필터링

  • 플래그를 사용하는 방법은 실전에서는 사용하면 안되는 방법이다.
public class AppleFilter {
    public static List<Apple> filterApples(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;
    }
}

2.2 동작 파라미터화

  • 사과의 어떤 속성에 기초해서 불리언 값을 반환하는 프레디케이트 인터페이스를 정의한다.
public interface ApplePredicate {
    boolean test(Apple apple);
}
``

```java
public class AppleHeavyWeightPredicate implements ApplePredicate {
    @Override
    public boolean test(Apple apple) {
        return apple.getWeight() > 150;
    }
}

public class AppleGreenColorPredicate implements ApplePredicate {
    @Override
    public boolean test(Apple apple) {
        return Color.GREEN.equals(apple.getColor());
    }
}

public class AppleRedAndHeavyPredicate implements ApplePredicate {
    @Override
    public boolean test(Apple apple) {
        return Color.RED.equals(apple.getColor()) && apple.getWeight() > 150;
    }
}
public class AppleFilter {
    public static List<Apple> filterApples(List<Apple> inventory, ApplePredicate p) {
        List<Apple> result = new ArrayList<>();
        for (Apple apple : inventory) {
            if (p.test(apple)) {
                result.add(apple);
            }
        }
        return result;
    }
}
List<Apple> redAndHeavyApples = AppleFilter.filterApples(inventory, new AppleRedAndHeavyPredicate());

2.3 복잡한 과정 간소화

  • ApplePredicate 인터페이스를 구현하는 여러 클래스를 정의한 다음에 인스턴스화 한 뒤, filterApples 메소드에 전달한다.
  • 익명 클래스를 이용한다면?
List<Apple> redApples = AppleFilter.filterApples(inventory, new ApplePredicate() {
    @Override
    public boolean test(Apple apple) {
        return Color.RED.equals(apple.getColor());
    }
});
  • 람다식
List<Apple> redApples = AppleFilter.filterApples(inventory, (Apple apple) -> Color.RED.equals(apple.getColor()));
  • 제네릭
public interface Predicate<T> {
    boolean test(T t);
}
public class Filter {
    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;
    }
}
List<Apple> filter = Filter.filter(inventory, (Apple apple) -> Color.RED.equals(apple.getColor()));

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> evenNumbers = Filter.filter(numbers, (Integer i) -> i % 2 == 0);

2.4 실전 예제

  • 동작 파라미터화 패턴은 동작을 캡슐화한 다음에 메서드로 전달해서 메서드의 동작을 파라미터화한다.
  • 디자인 패턴으로 보면 전략 패턴이다.

2.4.1 Comparator 정렬

  • 자바 8의 List의 sort 메서드의 파라미터인 Comparator 인터페이스를 구현하여 sort 메서드의 동작을 정할 수 있다.
public interface Comparator<T> {

  /* a negative integer, zero, or a positive integer as the
     first argument is less than, equal to, or greater than the
     second.
  */
    int compare(T o1, T o2);
}
inventory.sort(new Comparator<Apple>() {
    @Override
    public int compare(Apple o1, Apple o2) {
        return o1.getWeight() - o2.getWeight();
    }
});

inventory.sort((o1, o2) -> o1.getWeight() - o2.getWeight());

2.4.2 Runnable로 코드 블록 실행

  • 스레드에 실행 할 코드 블록을 지정할 수 있다.
Thread t = new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello world");
    }
});

Thread t = new Thread(() -> System.out.println("Hello world"));

Tags:

Categories:

Updated:

Leave a comment