[모던 자바 인 액션] chap02. 동적 파라미터화 코드 전달하기

2023. 2. 18. 18:00Java

동적 파라미터화란? - 아직은 어떻게 실행할 것인지 결정하지 않은 코드 블록

예제 코드

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> 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가 정확히 뭘 요구하는지를 for문의 순서를 따라가야 하는 괴상한 코드가 되어버렸다. 계속 요구사항이 바뀔때마다 이런식으로 파라미터화 시킨다면 여간 난처한 상황이 아닐것이다.

인터페이스로 조건을 만든다면

public interface ApplePredicate {
    boolean test(Apple apple);
}
public class AppleHeavyWeightPredicate implements ApplePredicate {
    @Override
    public boolean test(Apple apple) {
        return apple.getWeight() > 150;
    }
}
public class AppleGreenColorPredicate implements ApplePredicate {
    @Override
    public boolean test(Apple apple) {
        return GREEN.equals(apple.getColor());
    }
}
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;
}

코드 중복에 전략패턴을 쓰는 방법도 나름의 방법이지만, 동적 파라미터화된 코드가 많아진다면 interface를 상속하는 코드마다 구현하는 것도 귀찮아질 수 있다. 이렇게 인스터스화 될 코드를 만드는 방법보다 인스터스화와 동시에 코드 구현을 수행할 수 있도록 익명 클래스를 제공한다. 하지만 익명 클래스가 모든 귀찮음과 어려움을 해결하는 것은 아니다.

익명 클래스란? - 자바의 지역 클래스와 비슷한 개념이다. 말 그대로 이름이 없는 클래스다. 익명 클래스를 이용하면 클래스 선언과 인스턴스화를 동시에 할 수 있다. 즉석에서 필요한 구현을 만들어서 사용할 수 있다.

익명 클래스가 가질 수 있는 문제

  1. 익명 클래스는 많은 공간을 차지한다. (코드를 장황하게 만든다)
  2. 많은 프로그래머가 익명 클래스의 사용에 익숙치 않다.
apples = filterApples(apples, new ApplePredicate() {
	@Override
	public boolean test(Apple apple) {
		return apple.getColor().equals(Color.RED);
	}
});
        
// 익명 클래스를 람다로 바꾼다
apples = filterApples(apples, apple -> apple.getColor().equals(Color.RED));

코드 추상화

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> evenNumbers = 
	filter(numbers, (Integer num) -> num%2 == 0);

// T로 추상화 시킬수도 있다.​