동작 파라미터화 (Behavior Parameterization)
는 "다양한 동작을 수행할 수 있는 코드 블럭을 메서드 파라미터로 전달"함으로써 자주 변경되는 요구사항에 효과적으로 대응할 수 있는 방식을 의미합니다.
여기서는 자주 변경되는 요구사항의 예시를 기반으로 동작 파라미터화를 한 후 Java8에서 추가된 Lambda로 코드를 간소화하는 과정을 보여줍니다.
요구사항
우리는 농장 재고 애플리케이션을 개발합니다. 농부는 녹색 사과
만 필터링 하는 기능을 추가하길 원했습니다.
시도 1 : 녹색 사과 필터링
아래와 같이 녹색 사과만 필터링하는 메서드를 구현했습니다.
그런데 농부가 빨간 사과
를 필터링하는 기능을 추가하길 원했습니다.
public static List<Apple> filterGreenApples(List<Apple> inventory) {
List<Apple> result = new ArrayList<>();
for (Apple apple : inventory) {
if ("green".equals(apple.getColor())) {
result.add(apple);
}
}
return result;
}
시도 2 : 색을 파라미터화
메서드의 파라미터로 색을 추가하여 유연하게 개선했습니다.
그런데 이번엔 150g 이상 무게가 나가는 사과
를 필터링하길 원했습니다. 즉, 필터의 기준이 계속해서 변경될 가능성이 존재합니다.
아래와 같이 파라미터(color, weight)를 다르게 하여 원하는 결과를 얻을 수 있지만 중복된 코드가 존재합니다.
/**
* 색(Color)를 기준으로 필터링
*/
public static List<Apple> filterApplesByColor(List<Apple> inventory, String color) {
List<Apple> result = new ArrayList<>();
for (Apple apple : inventory) {
if (apple.getColor().equals(color)) {
result.add(apple);
}
}
return result;
}
/**
* 무게(Weight)를 기준으로 필터링
*/
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;
}
시도 3 : 가능한 모든 속성으로 필터링
앞서 농부가 요구했던 "색"과 "무게" 요구사항을 모두 합친 필터링 메서드를 구현합니다.
그러나 이 방식은 많은 문제점이 존재합니다.
만약 농부가 "150g 이상의 빨간 사과를 필터링"하고 싶어 한다면 또다시 코드가 중복되는 비슷한 메서드를 추가하거나, 조건절이 늘어나는 거대한 메서드를 만들게 될 것입니다.
또한 파라미터의 flag
값의 의미를 단번에 알 수 없습니다.
public static List<Apple> filterApples(List<Apple> inventory, String 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, "", 150, false);
시도 4 : 동작 파라미터화
사과의 어떤 속성에 기초하여 Boolena 값을 반환하는 메서드를 가진 인터페이스를 사용합니다. 이와 같은 동작을 프레디케이트 (Predicate)
라고 합니다.
Predicate 의 사전적 의미는 "술어" 입니다.
술어 의 사전적 의미는 "논리의 판단. 명제에서 주사에 대하여 긍정 또는 부정의 입언을 하는 개념" 입니다.
아래의 예제에선 전략 디자인 패턴 (Strategy Design Pattern)
을 사용하여 사과를 선택하는 조건을 캡슐화 했습니다.
전략 디자인 패턴 (Strategy Design Pattern)은 각 알고리즘을 캡슐화하는 알고리즘 패밀리를 정의해 두고, 런타임에 알고리즘을 선택하는 기법입니다.
여기서 "알고리즘 패밀리"는 AppleHeavyWeightPredicate
메서드와 AppleGreenColorPredicate
메서드`가 됩니다.
만약 농부가 새로운 필터링 조건을 요구하더라도 ApplePredicate 인터페이스
를 구현하는 메서드를 추가하면 됩니다.
이와 같이 메서드의 파라미터로 코드 블럭을 전달함으로써 메서드의 동작을 파라미터화 한 것 입니다.
public interface ApplePredicate {
boolean test (Apple apple);
}
public class AppleHeavyWeightPredicate implements ApplePredicate {
public boolean test(Apple apple) {
return apple.getWeight() > 150;
}
}
public class AppleGreenColorPredicate implements ApplePredicate {
public boolean test(Apple apple) {
return "green".equals(apple.getColor());
}
}
// 색과 무게를 동시에 필터링 조건으로 갖는 메서드(전략 or 알고리즘 패밀리) 추가
public class AppleRedAndHeavyPredicate implements ApplePredicate {
public boolean test(Apple apple) {
return "red".equals(apple.getColor()) && apple.getWeight() > 150;
}
}
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> greeApples = filterApples(inventory, new AppleGreenColorPredicate());
// 무게가 150g 이상인 사과만 필터링
List<Apple> heavyApples = filterApples(inventory, new AppleHeavyWeightPredicate());
시도 5 : 익명 클래스를 통한 코드 간소화
앞선 예제는 동작 파라미터화를 위해 여러 개의 클래스를 정의했습니다.익명 클래스 (Anonymous Class)
를 통해 클래스 정의 없이 필터링 구현이 가능합니다.
그러나 자칫하면 익명 클래스로 인해 코드가 장황해져 유지보수가 어려워 질 수 있습니다.
// 빨간 사과만 필터링
List<Apple> redApples = filterApples(inventory, new ApplePredicate() {
public boolean test(Apple apple) {
return "red".equals(apple.getColor());
}
});
시도 6 : 람다 표현식 사용
자바8의 람다 표현식으로 위 예제 코드를 간단하게 구현할 수 있습니다.
List<Apple> redApples = filterApples(inventory, (Apple apple) -> "red".equals(apple.getColor()));
시도 7 : 리스트 형식으로 추상화
사과 이외에도 다양한 물건을 필터링 할 수 있도록 추상화 할 수 있습니다. (Generic 사용)
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) -> "red".equals(apple.getColor()));
// 짝수 필터링
List<String> evenNumbers = filter(numbers, (Integer i) -> i % 2 == 0);
'프로그래밍 > Java & Spring Framework' 카테고리의 다른 글
스트림(Stream) 활용 (0) | 2019.11.16 |
---|---|
스트림 (Stream) (0) | 2019.11.16 |
람다 표현식 (Lambda Expression) (0) | 2019.11.16 |
Private 메소드 단위 테스트하기 (0) | 2019.11.16 |