[Java] 람다 함수의 필요성 - 동작 파라미터화와 간결성

호성·2022년 1월 26일
0
post-thumbnail

🎃

비즈니스 요구사항은 늘 변할 수 있다.

유연한 코드 설계에 대해, 그리고 람다 함수의 필요성 중 하나를 말하기 위한 긴 배경 얘기를 해보자.

색깔이 초록색인 사과 목록을 찾아주세요.

아래와 같은 enum을 작성하고,

enum Color { RED, GREEN }

필터 함수를 작성한다.

public static List<Apple> filterByGreenApples(List<Apple> inventory) {
    List<Apple> result = new ArrayList<>();
    
    for (Apple apple: inventory) {
    	if(GREEN.equals(apple.getColor()) {
            result.add(apple);
        }
    }

    return result;
}

초록색 말고 빨간색 사과 목록도 찾고 싶어졌어요.

나름대로 생각을 해본다. 혹시 상한 사과 색까지 찾고 싶을지도 모르니까.. 색에 유연하게 대처할 수 있도록 작성해보자.

public static List<Apple> filterAppleByColor(List<Apple> inventory, Color color) {
    List<Apple> result = new ArrayList<>();
    
    for (Apple apple: inventory) {
    	if(apple.getColor().equals(color)) {
            result.add(apple);
        }
    }

    return result;
}

아, 색 말고 무게로도 목록을 찾을 수 있었음 좋겠네요.

약간 당황하긴 했지만, 고민 끝에 그냥 메소드를 하나 더 작성하기로 했다.

public static List<Apple> filterAppleByColor(List<Apple> inventory, Color color) {
    List<Apple> result = new ArrayList<>();
    
    for (Apple apple: inventory) {
    	if(apple.getColor().equals(color)) {
            result.add(apple);
        }
    }

    return result;
}

public static List<Apple> filterAppleByWeight(List<Apple> inventory, int weight) {
    List<Apple> result = new ArrayList<>();
    
    for (Apple apple: inventory) {
    	if(apple.getWeight() > weight) {
            result.add(apple);
        }
    }

    return result;
}

뭔가 맘에 들지 않는다. 색깔 필터 메소드와 무게 필터 메소드는 분명 합치기에는 애매한 메소드지만 너무 겹치는 코드들이 많이 존재한다.

변화하는 요구사항에 유연하게 대비하기 위해 어떠한 기준으로 사과를 필터링할 지에 대한 것을 파라미터화 하면 어떨까?

공통된 사항을 추상화하자.

사과가 초록색인가요? true / false
사과가 빨간색인가요? true / false
사과가 100g보다 무거운가요? true / false

조건의 결과는 결국 boolean 형태로 귀결됨을 알 수 있다.

그래서, 참 / 거짓을 반환하는 메소드를 인터페이스화 해보자.

public interface ApplePredicate {
	boolean conditionTest(Apple apple);
}

참/ 거짓을 반환하는 메소드를 Predicate라고 한다.

그리고 위 인터페이스를 원하는 조건에 따라 구현한다.

public class AppleHeavyWeightPredicate implements ApplePredicate {
	public boolean conditionTest(Apple apple) {
    	return apple.getWeight() > 100;   
    }
}


public class AppleGreenColorPredicate implements ApplePredicate {
	public boolean conditionTest(Apple apple) {
    	return GREEN.equals(apple.getColor());   
    }
}

public class AppleRedAndHeavyPredicate implements ApplePredicate {
	public boolean conditionTest(Apple apple) {
    	return RED.equals(apple.getColor()) && apple.getWeight() > 100;   
    }
}

기존 사과 필터링 메소드 정의를 한걸음 물러나 살짝 추상화 해본다.

public static List<Apple> filterApples(List<Apple> inventory, ApplePredicate p) {
    List<Apple> result = new ArrayList<>();
    
    for (Apple apple: inventory) {
    	if(p.conditionTest()) {
            result.add(apple);
        }
    }

    return result;
}

위와 같이 정의하고, 호출해보자.

List<Apple> redAndHeavyApples = filterApples(inventory, new AppleRedAndHeavyPredicate());

대강 원하는 형태의 코드 설계가 끝났다.

인터페이스를 정의하고 이를 구현한 클래스를 파라미터로 전달함으로써 유연성을 극대화했다.

filterApples 메소드를 조건에 따라 새롭게 만들지 않아도 되었고 이는 동작을 파라미터화 함으로써 해결했다.

하지만, 여전히 만족스럽진 않은데.. 매번 conditionTest()를 구현하기 위해 클래스를 정의해야하고, 이를 new를 통해 인스턴스하여 파라미터로 전달해야한다.

위 작업 또한 상당히 번거로운 일이다.

Java 8의 람다 함수는 위 작업을 간소화 할 수 있다.

빨간 사과를 필터링 해보자.

List<Apple> result = filterApples(inventory, (Apple apple) -> RED.equals(apple.getColor()));

람다 함수는 이렇게 유연한 동작 파라미터화 방식을 사용할 수 있으면서, 코드의 장황함을 줄인 자바 8의 새로운 기능이다.

필요성을 이해할 수 있지 않은가?

약간 용두사미스러운 글이였지만, 그래도

요약

  • 값 파라미터화(맨처음 사용했던 유연성 떨어지는 코드) 방식은 변화하는 비즈니스 요구사항에 대한 유연성이 매우 떨어졌다.

  • 그래서 클래스를 통한 동작 파라미터화를 통해 유연성을 끌어올렸다. 다만, 클래스를 통한 동작 파라미터화는 여전히 장황한 코드라는 결과물을 남겼다.

  • JAVA 8의 람다 함수는 동작 파라미터화를 통한 유연성, 코드의 간결함 모두를 챙길 수 있는 기능이다.

profile
스프링 깎는 노인

0개의 댓글