tlov

동작 파라미터화 코드 전달하기 본문

개발

동작 파라미터화 코드 전달하기

nowitzki 2024. 10. 10. 00:25

#개발/자바/모던자바인액션

 

동작 파라미터화를 이용하면 자주 바뀌는 요구사항에 효과적으로 대응할 수 있습니다.

 

동작 파라미터란? 아직은 어떻게 실행할 것인지 결정하지 않은 코드 블록이다.

 

예를 들어, 나중에 실행될 메서드의 인수로 코드 블록을 전달할 수 있습니다. 결과적으로 코드 블록에 따라 메서드의 동작이 파라미터화 됩니다. 이를 이해하기 위해 책에 있는 하나의 예제를 통해 알아보겠습니다. 기존의 농장 재고목록 어플리케이션에 리스트에서 녹색 사과만 필터링 하는 기능을 추가한다고 가정합시다.

enum Color { RED, GREEN }

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

 

그런데 갑자기 농부가 여러 가지 다양한 색으로 필터링 하고싶어지면, 여러 메서드를 만들고 if문을 여러개 만들어야 할까요? 이런 상황에서는 다음과 같은 좋은 규칙이 있습니다.

 

거의 비슷한 코드가 반복 존재한다면 그 코드를 추상화한다.

 

 

2. 색을 파라미터화

색을 파라미터화할 수 있도록 메서드에 파라미터를 추가하면 변화하는 요구사항에 좀 더 유연하게 대응하는 코드를 만들 수 있습니다.

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

 

그럼 다음처럼 구현한 메서드를 호출할 수 있습니다.

List<Apple> greenApples = filterApplesByColor(inventory, GREEN);
List<Apple> redApples = filterApplesByColor(inventory, RED);

 

그런데 갑자기 농부가 다시 나타나서는 무게가 150그램 이상인 사과가 무거운 사과인데, 가벼운 사과와 무거운 사과로 구분할 수 있다면 좋을 것 같다고 합니다. 이전에, 색을 구현하면서 다양한 색에 대응할 수 있도록 메서드를 만들었으니 혹시 모르는 요구사항을 대비하기 위해 무게도 여러 무게를 기준으로 구분하도록 구현합니다.

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

 

좋은 해결책 같습니다. 하지만, 구현 코드를 자세히보면 for문으로 inventory를 탐색하고, 각 사과에 필터링 조건을 적용하는 부분의 코드가 색 필터링 코드와 대부분 중복됩니다. 그럼, 성능 개선을 위해 중복되는 부분을 수정하려면 모든 메서드를 수정하는 참사가 벌어질 것 같네요.

 

아예 색과 무게를 하나의 메서드로 합쳐 filter라는 메서드를 만들어봅시다.

 

 

3. 가능한 모든 속성으로 필터링

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

 

정말 더러운 코드가 되어버렸습니다. 농부의 요구사항이 점점 늘어나면 메서드의 인수도 점점 늘어나게 될텐데, 그러면 if문은 또 수정되어야 합니다. 이를 해결하기 위해 동작 파라미터화를 이용해보도록 합시다!

 

 

3.2 동작 파라미터화

앞선 예제를 통해 파라미터 추가가 아닌 변화하는 요구사항에 좀 더 유연하게 대응할 수 있는 방법이 필요하다는 것을 알게되었습니다. 선택 조건들을 좀 더 분석해봅시다.

 

사과의 속성에 기초해 불리언 값을 반환할 수 있습니다. (사과가 녹색인가? yes-no, 무게가 150그램 이상인가? yes-no). 이렇게 참 또는 거짓을 반환하는 함수를 프레디케이트라고 합니다. 선택 조건을 결정하는 인터페이스를 정의합시다.

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

 

그럼 다음처럼 다양한 선택조건에 대해 여러 버전의 ApplePredicate를 정의할 수 있습니다.

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

 

위 조건에 따라 filter 메서드가 다르게 동작할 것이라고 예상할 수 있습니다. 이를 전략 디자인 패턴이라고 합니다. 이것을 이용해서 filterApples에서 ApplePredicate 객체를 받아 애플의 조건을 검사하도록 메서드를 고쳐봅시다!

 

 

4. 추상적 조건으로 필터링

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 메서드의 동작이 결정됩니다. 즉, 우리는 filterApples 메서드의 동작을 파라미터화 한 것입니다!

 

한 개의 파라미터, 다양한 동작

컬렉션 탐색 로직과 각 항목에 적용할 동작을 분리한다는 것이 '동작 파라미터'의 강점입니다.

'개발' 카테고리의 다른 글

MVC 패턴  (3) 2024.10.15
BeanFactory와 ApplicationContext, Configuration  (0) 2024.10.04
스프링 부트를 이용한 페이징 구현  (2) 2024.09.27
JWT 토큰을 이용한 로그인 방법 이해  (5) 2024.09.15