아직은 어떻게 실행할 것인지 결정하지 않은 코드 블록을 의미한다. 메서드의 동작이 파라미터화가 된다.
사용하는 이유가 무엇일까?
여러가지 이유가 있겠지만 가장 핵심적으로 말씀해주시는 것은 변화하는 요구사항에 대응하기 위해서이다.
농부의 요구사항이다.
1. 사과가 초록색인지 확인해봐라~
2. 아니다. 초록색말고 빨강색인지도 확인해봐라.
3. 그 중에서 150그램 이상인 걸 찾아보자.
이러한 과정을 진행했을때 우리가 코드를 짠다면 다음과 같이 구성이 될 거다.
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;
}
//호출 시
List<Apple> greenApples = filterApples(inventory, GREEN, 0, true);
List<Apple> heavyApples = filterApples(inventory, null, 150, false);
요구사항에는 잘 맞추긴 했지만 여기에서 갑자기 요구사항이 바뀌게 된다면? 약간 골치 아픈 상황이 생길 거 같다.
그래서 책에서 말하는 바는 다음과 같다. 이제부터 어떤 기준으로 사과를 필터링할 것인지 효과적으로 전달할 수 있게 해보자.
아까 위에서 요구사항을 우리는 알고 있지만 불안 할 거다. 언제 이 요구사항을 바꿔달라할 지 모르니 말이다.
그래서 책에서는 Predicate
라는 것을 사용하기로 했다.
Predicate : 어떤 속성에 기초해서 불리언값을 반환(ex : 사과가 녹색인지? 사과가 150그램 이상인지?)하는 함수
그래서 다음과 같은 과정을 진행한다.
interface ApplePredicate {
boolean test(Apple a);
}
public class AppleWeightPredicate implements ApplePredicate {
@Override
public boolean test(Apple apple) {
return apple.getWeight() > 150;
}
}
public class AppleColorPredicate implements ApplePredicate {
@Override
public boolean test(Apple apple) {
return apple.getColor() == Color.GREEN;
}
}
사과 선택 전략에 따라 이렇게 filter가 동작하도록 만드는 것을 전략 디자인 패턴
이라고도 부르기로 했다.
전략 디자인 패턴 : 각 알고리즘을 캡슐화하는 알고리즘 패밀리를 정의해둔 다음에 런타임에 알고리즘을 선택하는 기법이다.
우리의 코드에서 보면 AppleWeightPredicate, AppleColorPredicate가 전략이고 ApplePredicate가 알고리즘 패밀리다.
이렇게 까지 설계를 했으면 이제 ApplePredicate라는 객체를 받아서 애플의 조건을 검사하도록 메서드를 수정해야 한다. 즉, 동작 파라미터화가 될 수 있도록 말이다. 위에 선정한 메서드의 다양한 전략을 받아서 다양한 동작을 수행할 수 있다.
public static List<Apple> filter(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> heavyApples = filterApples(inventory, new AppleWeightPredicate());
이런식으로 고치게 되면? ApplePredicate를 통해서 filter를 호출하게 될 때 더 동적으로 수행할 수 있을거다! 전.략 에 따라서 말이다.
우리가 호출하는 new AppleWeightPredicate()
이 친구를 통해서 우리가 메서드의 동작을 파라미터화 했다는 것을 알 수 있다.
그렇지만 아직 더 고칠게 남아있다. 불편하게 생각해보자.
위에서 우리가 굳이 이 Predicate하나를 위해서 클래스를 2개나 만드는 작업을 진행해야 했을까?
라는 생각을 해보자. 그러면 당연히 더 줄일 수 있으면 그런 방법을 찾을 거다.
이렇게 할 수 있는 방법 중 하나가 바로 익명 클래스
다.
익명 클래스 : 이름이 없는 클래스, 클래스 선언과 인스턴스화를 동시에 할 수 있다.
이제는 호출과 동시에 바로 메서드의 동작을 파라미터화 해보자.
List<Apple> heavyApples = filterApples(inventory, new AppleWeightPredicate(){
public boolean test(Apple apple){
return apple.getWeight() > 150;
}
});
엄청 많이 줄어들었다. 그렇지만 아직도 길다고 책에서는 말하고 있다. 여기서 더 줄일 수 있는 방법이 있긴 한데 왜 이렇게 줄였는데 만족하지 못할까?
크게 2 가지의 이유가 있었다고 한다.
그래서 이젠 더 줄여볼 것이다. 바로 람다
라는 것을 사용해서 말이다.
이제 자바 8의 장점인 람다라는 표현식을 사용해서 위 코드를 더 줄여보자.
List<Apple> heavyApple = (inventory, (Apple apple) -> apple.getWeight() > 150;)
그 길었고 유연하지 않았던 코드가 유연하면서 간결하게 한 줄로 표현이 되게 되었다.