본문 바로가기
자바

[모던 자바 인 액션] 동작 파라미터화(Behavior Parameterization)

by __Minnie_ 2025. 4. 16.

동작 파라미터화란 아직 어떻게 실행할 것인지 결정하지 않은 코드를 의미한다. 메서드의 인수로 코드 블록을 전달하는 것을 말하며, 결과적으로 메서드의 동작이 파라미터화된다. 

 

변화하는 요구사항에 대응하기

만약 우리가 사과 리스트에서 녹색 사과만을 추린다고 생각해보자 그러면 우리는 아래처럼 코드를 작성할 것이다. 

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;
}

 

그런데, 요구사항이 변경되어서 빨간색 사과를 추려야 하는 것으로 변경이 되었고, 또 얼마 되지 않아서 노란 사과를 추려야 하는 것으로 변경된다면 우리는 어떻게 할 수 있을까?

 

가장 간단하게 시도해볼 수 있는 방법은 색깔을 파라미터화 하는 것이다.

public static List<Apple> filterApplesByColor(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> filterApplesByWeight(List<Apple> inventory, int weight) {
    List<Apple> result = new ArrayList();
    for (Apple apple: inventory) {
        if (apple.getWeight() > weight) {
            result.add(apple);
        }
    }

    return result;
}

 

우리는 이렇게 코드를 작성할 수 있다. 그런데, 이렇게 작성하게 되면 색상 필터링 메소드와 겹치는 부분이 매우 많아진다. 이는 DRY(don't repeat yourself) 원칙을 어기는 것이다.

 

그러면 두개의 필터링을 합친다면 문제를 해결할 수 있을까?

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;
}

 

이 코드는 딱 봤을 때, 무엇을 하는 코드인지 알기가 어렵다. flag가 어떤 역할을 하는지 알아내기도 어렵다. 

 

우리는 이런 경우에 동작 파라미터화를 이용해서 유연성을 얻을 수 있다. 

 

동작 파라미터화

동작 파라미터화를 사용하기 위해서 먼저 참 또는 거짓을 반환하는 프레디케이트(Predicate) 인터페이스를 정의한다.

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

 

이 프레디케이트를 구현해서 다양한 기준의 사과 프레디케이트를 만들어낼 수 있다.

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

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

이렇게 기본이 되는 인터페이스를 구현해두고 런타임시에 실행할 알고리즘을 선택하는 방법을 전략패턴이라고 한다.

 

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;
}

filterApples(inventory, new AppleGreenColorPredicate());
filterApples(inventory, new AppleHeavyWeightPredicate());

 

filterApple메소드가 이 ApplePredicate를 파라미터로 받아서 필터링을 적용하게 하면, 코드의 중복도 없앨 수 있고, 코드를 보았을 때, 어떤 기준으로 필터링을 하는지 한눈에 알 수 있게 된다.

 

 

복잡한 과정 단순화

우리는 위에서 동작 파라미터화를 사용해서 코드를 굉장히 유연하게 변경하였다. 그러나, 하나의 필터를 적용하기 위해서 우리는 인터페이스를 구현하는 클래스를 정의하고 인스턴스화를 해야 한다. 이는 굉장히 복잡하다. 

 

우리는 익명 클래스를 활용해서 코드를 좀 더 단순하게 만들 수 있다. 익명 클래스는 이름이 없는 클래스로 선언과 인스턴스화를 동시에 할 수 있다. 

filterApples(inventory, new ApplePredicate(){
	public boolean test(Apple apple) {
    	return RED.equals(apple.getColor());
    }
});

 

 

그러나, 여전히 클래스를 선언하는 쓸데 없는 부분이 많은 자리를 차지하고 있다. 이를 람다를 이용해서 더 간단하게 바꿀 수 있다.

filterApples(inventory, (Apple apple) -> RED.equals(apple.getColor()));

 

굉장히 간단해진 것을 확인할 수 있는데, 만약에 사과가 아닌 오렌지에 대해서 필터링이 필요한 것으로 요구사항이 변경되면 우리는 인터페이스부터 새로 생성을 해야 할까? 이 문제는 제네릭을 통한 타입 추상화를 통해서 해결할 수 있다.

 

public interface Predicate {
	boolean test(T t);
}

public static List<T> filter(List<T> inventory, Predicate<T> p) {
    List<T> result = new ArrayList();
    for (T t: inventory) {
        if (p.test(t)) {
            result.add(t);
        }
    }

    return result;
}

filter(inventory, (Apple apple) -> RED.equals(apple.getColor()));
filter(inventory, (Orange orange) -> YELLOW.equals(orange.getColor()));

 

이렇게 우리는 필터링 조건이 변경 되어도, 필터링 대상이 변경되어도 모두 대응 할 수 있는 유연하고 간결한 코드를 얻게 되었다. 

'자바' 카테고리의 다른 글

[모던 자바 인 액션] 람다 표현식  (0) 2025.04.17