아직 어떻게 실행할 것인지 결정하지 않은 코드 블록. 나중에 프로그램에서 호출해서 사용하는 것. 즉, 코드 블럭의 실행이 나중으로 미뤄진다. 요구 사항은 언제든 변화할 수 있기 때문에 이처럼 코드 블럭의 실행을 나중으로 미루면 효과적으로 대응할 수 있다.
메서드의 인자로 코드 블럭을 전달할 수 있기 때문에, 코드 블럭에 따라 메서드의 내부 동작이 결정되고, 이를 동작이 파라미터화 된다고한다.
요구사항이 변하게 된다면 메서드의 동작도 변화되어야 한다. 하지만 요구사항이 변경될 때 마다 메소드에 파라미터를 추가하여 처리하는 방법에는 한계가 존재한다. 따라서 요구사항에 유연하게 대응할 수 있는 방법이 필요하다.
거의 비슷한 코드가 반복 존재한다면 그 코드를 추상화한다.
요구 사항이 계속 변할 때, 인터페이스를 사용해서 해결하는 방법이 있다.
1) 선택 조건을 결정하는 인터페이스 정의
public interface ApplePredicate {
boolean test(Apple apple);
}
2) 요구사항에 맞는 인터페이스를 구현하기
// 무거운 사과만 선택하기
public class AppleHeavyWeightPredicate implements ApplePredicate {
public boolean test(Apple apple) {
return apple.getWeight() > 150;
}
}
// 무거운 사과만 선택하기
public class AppleGreenColoePredicate implements ApplePredicate {
public boolean test(Apple apple) {
return GREEN.equals(apple.getColor());
}
}
메서드는 이렇게 생성한 다양한 전략을 받아서 내부적으로 다양한 동작을 수행할 수 있다.
아래와 같이 전략을 인수로 받는 메서드는 내부 로직과 동작(전략)이 분리된다. 전략에서 수행하는 동작이 캡슐화 된 것이다.
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> redApples = filterApples(inventory, new ApplePredicate() {
public boolean test(Apple apple) {
return RED.equals(apple.getColor());
}
});
하지만 익명 클래스의 사용은 공간을 많이 차지한다는 단점이 있다. 또 가독성이 좋지않다.
익명 클래스의 단점을 보완하기 위해 람다식을 사용할 수 있다.
List<Apple> result = filterApples(inventory, (Apple apple) -> RED.eqauls(apple.getColor()));
만약 Apple 뿐만 아니라 다른 타입에도 위의 filter 메서드를 사용하고 싶다면 다른 메서드로 생성해야할까?
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;
}
메서드를 이렇게 추상화하면 아래와 같이 Apple 뿐만아니라 다른 타입으로도 얼마든지 사용할 수 있다.
List<Apple> redApples = filter(inventory, (Apple apple) -> RED.equals(apple.getColor()));
List<Integer> evenNumbers = filter(numbers, (Integer i) -> i % 2 == 0);
1) 동작을 캡슐화한다.
2) 캡슐화한 동작을 메서드로 전달한다. - 파라미터화