Contents
동적 파라미터를 예제를 통해서 배워보자.
   Aug 29, 2022     7 min read

CHAPTER2

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

  • 변화하는 요구사항에 대응
  • 동작 파라미터화
  • 익명 클래스
  • 람다 표현식




요구 사항을 가정해보자 !

  • 농부(클라이언트)는 첫 째날에 녹색 사과를 모두 찾고싶다고 말했다.
  • 둘째 날 : 다음날 150그램 이상 사과를 모두 찾고 싶다라고 재요청 했다.
  • 셋째 날 : 150그램이상 녹색사과를 모두 찾고 싶다고 한다.

이런 변덕이 심한 농부의 요구 사항을 어떻게 대처할까 ?

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

그렇다면 동작파라미터화는 무엇일까 ??




동작 파라미터화(behavior parameterization)

  • 아직은 어떻게 실행할 것인지 결정하지 않은 코드 블록을 의미 즉, 코드 블록의 실행은 나중으로 미뤄진다.

예를 들어 나중에 실행될 메서드의 인수로 코드 블록을 전달할 수 있다.

결과적으로 코드 블록에 따라 메서드의 동작이 파라미터화된다.

예를 들어 컬렉션을 처리할 때 다음과 같은 메서드를 구현한다고 가정하자.

  • 리스트의 모든 요소에 대해서 ‘어떤 동작’을 수행할 수 있음
  • 리스트 관련 직업을 끝낸 다음에 ‘어떤 다른 동작’을 수행할 수 있음
  • 에러가 발생하면 ‘정해진 어떤 다른 동작’을 수행할 수 있음

자 ! 이제 상황을 코드로 설명하면서 해보자.




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

첫 번째 시도 : 녹색 사과 필터링

enum Color {
	RED, GREEN
}

public static List<Apple> filterGreenApples<List<apple>> inventory){
	List<Apple> result = new ArrayList<>(); // 사과 누적 리스트
	for(Apple apple : inventory){
		if(GREEN.eqauls(apple.getColor()){ // 녹색 사과만 선택
					result.add(apple);
			}
	}
}

농부가 요구사항을 녹색말고 빨간 사과도 필터링하고 싶다고한다. 어떻게 고쳐야할까 ?

  1. filterGreenApples()를 복사해서 새로운 메서드를 만들어 GREEN을 RED로 바꾸는 방법

1번 문제점 : 농부가 더 다양한 색(옅은 녹색, 어두운 빨간색, 노란색 등)으로 필터링하는 등의 변화를 요구할 시 적절하게 대응할 수 없다.

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




두 번째 시도 : 색을 파라미터화

  • 어떻게 해야 filterGreenApples의 코드를 반복 사용하지 않고 filterReadApples를 구현할 수 있을까 ?

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

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

구현한 메소드를 호출할 수 있다.

List greenApples = filterApplesByColor(inventory, GREEN);

List redApples = filterApplesByColor(inventory, RED);

농부의 다양한 요구사항에서 색과 마찬가지로 무게의 기준도 얼마든지 바뀔 수 있다.

다양한 무게에 대응할 수 잇도록 무게 정보 파라미터도 추가했다.

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) 원칙을 어기는 것

  • 그렇다면 탐색과 과정을 고쳐서 성능을 개선하려면 무슨 일이 일어날까 ?

한 줄이 아니라 메서드 전체 구현을 고쳐야 한다. 즉 엔지니어링적으로 비싼 대가를 치러야 한다.

색과 무게를 filter라는 메서드로 합치는 방법도 있다. 하지만 어떤 기준으로 사과를 필터링할지 구분하는 또 다른 방법이 필요하다. 따라서 색이나 무게 중 어떤 것을 기준으로 필터링할지 가리키는 플래그(flag)를 추가할 수 있다.

(※주의 : 실전에서 절대 이 방법을 사용하지 말아야 한다. )

이유는 밑에서 !




세 번째 시도 : 가능한 모든 속성으로 필터링

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;
}
System.out.println("---------가능한 모든 속성으로 필터링---------");
        List<Apple> greenApples1 = filterApples(inventory, GREEN, 0, true);
        System.out.println(greenApples1);

형편없는 코드이다. 대체 true와 false는 어떤걸 의미하는 걸까? 그리고 앞으로 요구사항이 바뀌었을 때 유연하게 대응할 수도 없다. 예를 들어 사과의크기, 모양, 출하지 등으로 사과를 필터링하고 싶다면 어떻게 될까? 녹색 사과 중에 무거운 사과를 필터링하고 싶다면? 결국 여러 중복된 필터 메서드를 만들거나 아니면 모든 것을 처리하는 거대한 하나의 필터 메서드를 구현해야 한다.

filterApples에 어떤 기준으로 사과를 필터링할 것인지 효과적으로 전달할 수 있다면 더 좋을 것이다.

  • 이것을 극복하기 위해서 동작 파라미터화 를 이용해서 유연성을 얻는 방법을 알아보자.




Predicate ? 전략 디자인 패턴

  • 네 번째 시도하기 전에 새로운 개념을 배워보고 가자.

세 번째와 같이 파라미터를 추가하는 방법이 아닌 변화하는 요구사항에 좀 더 유연하게 대응할 수 있는 방법이 절실하다는 것을 확인했다.

선택 조건을 다음 처럼 결정할 수 있다. 사과의 어떤 속성에 기초해서 불리언값을 반환(예를 들어 사과가 녹색인가? 150그램 이상인가?) 하는 방법이 있다. 참 또는 거짓을 반환하는 함수를 Predicate라고 한다.

선택 조건을 결정하는 인터페이스

  • 선택 조건을 결정하는 인터페이스
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());
    }
}

Untitled

위 조건에 따라 filter 메서드가 다르게 동작할 것이라고 예상할 수 있다.

이를 전략 디자인 패턴 이라고 한다.

Tip. 전략 디자인 패턴은 각 알고리즘(전략이라 불리는)을 캡슐화하는 알고리즘 패밀리를 정의 해둔 다음에 런타임에 알고리즘을 선택하는 기법이다.

  • 예제에서는 ApplePredicate가 알고리즘 패밀리고 AppleHeavyWeightPredicate와 AppleGreenColorPredicate가 전략이다.


  • 그런데 ApplePredicate는 어떻게 다양한 동작을 수행할 수 있을까 ?

filterApples에서 ApplePredicate 객체를 받아 애플의 조건을 검사하도록 메서드를 고쳐야 한다.
이렇게 동작 파라미터화, 즉 메서드가 다양한 동작 (또는 전략)을 받아서 내부적으로 다양한 동작을 수행할 수 있다.
이제 filterApples 메서드가 ApplePredicate 객체를 인수로 받도록 고치자.
이렇게 하면 filterApples 메서드 내부에서 컬렉션을 반복하는 로직과 컬렉션의 각 요소에 적용할 동작(예제에서 Predicate)을 분리할 수 있다는 점에서 소프트웨어 엔지니어링적으로 큰 이득을 얻는다.

네 번째 시도 : 추상적 조건으로 필터링

ApplePredicate를 이용한 필터 메서드

public static List<Apple> filterApples(List<Apple> inventory, ApplePredicate p){

	List<Apple> result = new ArrayList<>();
	for(Apple apple : inventory){
		if(p.test(apple)){ // Predicate 객체로 사과 검사 조건을 캡슐화했다.
			result.add(apple);
		}
	}
	return result;
}

이제 예를 들어 농부가 150그램이 넘는 빨간 사과를 검색해달라고 부탁하면 우리는 ApplePredicate를 적절하게 구현하는 클래스만 만들면 된다. !!!

public class AppleRedAndHeavyPredicate implements ApplePredicate{
    @Override
    public boolean test(Apple apple) {
        return RED.equals(apple.getColor()) && apple.getWeight() > 150;
    }

}
List<Apple> redAndHeavyApples = filterApples(inventory, new AppleRedAndHeavyPredicate());
  • filterApples의 동작을 파라미터화하고 다양한 필터 전달하기

Untitled 1

filterApples 메서드의 새로운 동작을 정의하는 것이 test 메서드이다. 메서드는 객체만 인수로 받으므로 test메서드를 ApplePredicate객체로 감싸서 전달해야 한다.

test메서드를 수현하는 객체를 이용해서 불리언 표현식 들을 전달할 수 있으므로 이는 ‘코드를 전달' 할 수 있는 것이나 다름없다.




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

지금까지 살펴본 것처럼 컬렉션 탐색 로직과 각 항목에 적용할 동작을 분리할 수 있다는 것이 동작 파라미터화의 강점이다.

  • filterApples의 동작을 파라미터화 하고 다양한 필터 전략을 전달

Untitled 2




동작 파라미터를 배우고…. 느낀점

단계 별로 하나씩 정리해 나아가니깐 훨씬 이해가 잘되고, 코드의 변천을 보며 아 이게 이전 보다 더 나은 코드를 짠다는 것이 이런 말이구나라는 것을 좀 알게되었다. 아직 익숙하지않지만 자꾸 안보고 생각해보고 코드를 보고 이 개념을 적용하려고 해봐야겠다.