2025. 1. 7. 23:14ㆍBook/모던 자바 인 액션
목차.
1. 변화하는 요구사항에 대응
2. 동작 파라미터화
3. 익명 클래스
4. 람다 표현식 미리보기
동작 파라미터화를 이용하면 자주 바뀌는 요구사항에 효과적으로 대응할 수 있다.
동작 파라미터화란 아직은 어떻게 실행할 것인지 결정하지 않은 코드 블록을 의미한다. 이 코드 블록은 나중에 실행될 메서드의 인수로 코드 블록을 전달할 수 있다.
1. 변화하는 요구사항에 대응하기
예제를 보면서 변화에 대응하는 코드를 구현해보자. 기존의 농장 재고목록 애플리케이션에 리스트에 녹색 사과만 필터링하는 기능을 추가한다고 가정하자.
1.1 첫 번째 시도 : 녹색 사과 필터링
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;
}
녹색 사과만 선택하는 조건행이 보일 것이다. 갑자기 고객의 요구사항이 변해 빨간 사과도 필터링하고 싶어졌다면 어떻게 고쳐야할까?
단순하게 생각해보면 위 메서드를 복사 붙여넣기 하고, 조건문을 수정해 filterRedApple 메서드를 만들 수 있다. 이와 같은 방법은 좀 더 다양한 색으로 필터링하는 등의 변화에는 적절하게 대응할 수 없다.
위와 같은 상황에서 비슷한 코드가 반복 존재한다면 추상화하는 방법이 있다.
1.1 두 번째 시도 : 색을 파라미터화
filterGreenApples의 코드를 반복 사용하지 않고 filterRedApples를 구현해보자.
메서드에 색을 파라미터로 받는다면 색과 관련한 요구사항의 변화에 좀 더 유연하게 대응할 수 있는 코드를 짤 수 있다.
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;
}
이제 다음처럼 구현한 메서드를 호출할 수 있다.
List<Apple> greenApples = filterApplesByColor(inventory, Color.GREEN);
List<Apple> redApples = filterApplesByColor(inventory, Color.RED);
하지만 색 이외에 무게를 조건에 추가해달라는 요청이 온다면?
무게 파라미터를 메서드에 추가하고 또 추가 요청사항에 대해서도 메서드에 마라미터를 추가해야 하는 상황이 발생할 수 있다.
위와 같은 상황에서는 동작 파라미터화를 이용해서 유연성을 얻을 수 있다.
2. 동작 파라미터화
사과의 어떤 속성에 기초해서 불리언값을 반환하는 방법이 있다.
참 또는 거짓을 반환하는 함수를 프레디케이트라고 한다.
선택 조건을 결정하는 인터페이스를 정의하자.
public interface ApplePredicate {
boolean test (Apple apple);
}
다음 예제처럼 다양한 선택 조건을 대표하는 여러 버전의 ApplePredicate를 정의할 수 있다.
public class AppleGreenColorPredicate implements ApplePredicate{
@Override
public boolean test(Apple apple) {
//무거운 사과만 선택
return apple.getWeight() > 150;
}
}
public class AppleHeavyWeightPredicate implements ApplePredicate{
@Override
public boolean test(Apple apple) {
//녹색 사과만 선택
return Color.GREEN.equals(apple.getColor());
}
}
filterApples에서 ApplePredicate 객체를 받아 애플의 조건을 검사하도록 메서드를 고쳐야 한다.
2.1 세 번째 시도 : 추상적 조건으로 필터링
다음은 ApplePredicate를 이용한 필터 메서드다.
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;
}
이제 필요한 대로 다양한 ApplePredicate를 만들어서 filterApples 메서드로 전달할 수 있다.
만약 빨간색 사과, 무게는 150 이상인 사과만 필터링 해달라는 요청사항이 온다면 해당 조건으로 ApplePredicate를 만들어서 filterApples 메서드에 전달하면 된다.
public class AppleRedAndHeavyPredicate implements ApplePredicate{
@Override
public boolean test(Apple apple) {
return Color.RED.equals(apple.getColor())
&& apple.getWeight() > 150;
}
}
List<Apple> redAndHeabyApple =
filterApples(inventory, new AppleRedAndHeavyPredicate());
2.2 네 번째 시도 : 익명 클래스, 람다 사용
위에서 사용한 전략 패턴은 인터페이스를 만들고 그 구현체를 만들어야 하는 번거로운 작업을 거쳐야 한다. 클래스의 선언과 인스턴스화를 동시에 할 수 있는 익명 클래스를 사용하면 코드의 양을 줄일 수 있다.
List<Apple> redApples = filterApples(inventory, new ApplePredicate() {
@Override
public boolean test(Apple apple) {
return Color.RED.equals(apple.getColor());
}
});
List<Apple> redApples =
filterApples(inventory, apple -> Color.RED.equals(apple.getColor()));
2.3 다섯 번째 시도 : 리스트 형식으로 추상화
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;
}
이제 사과뿐 아니라 오렌지, 바나나, 정수, 문자열 등의 리스트에 필터 메서드를 사용할 수 있다.
List<Apple> redApples =
filter(inventory, (Apple apple) -> Color.RED.equals(apple.getColor()));
List<Integer> evenNumners =
filter(numbers, (Integer i) -> i % 2 == 0);