자바 뚝딱거리기

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

bimppap 2021. 2. 26. 01:32

시시각각 변하는 사용자 요구사항에 어떻게 대응해야 할까?

엔지니어링적인 비용이 최소화되고 추가되는 기능을 쉽게 구현할 수 있으며 장기적으로 유지보수가 쉬워야 하는 방식으로!

 

자주 바뀌는 요구사항에 효과적으로 대응할 수있는 동작 파라미터화를 소개해본다.

 

 

동작 파라미터화 behavior parameterization
어떻게 실행할 것인지 결정하지 않은 코드 블록

풀어 설명하자면 어떤 동작을 할 수 있으나, 아직 안 하고 있는 코드 블록을 메서드의 파라미터로 넘기는 방법이다.


예시로 사과 농장을 보자.

 

농부는 수확한 사과 중 녹색 사과만 골라보려고 한다.

동작 파라미터화를 쓰지 않고 녹색 사과를 가져오는 방법은 다음과 같다.

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

 

만일 농부가 도중에 변심하여 빨간 사과를 고르고 싶다고 하면 어떻게 해야 할까?

filterRedApples 메소드를 따로 만들어야 할까? 그 후에 다른 색의 사과를 또 요구한다면? 중복 코드가 늘어나게 될 것이다.

 이를 해결하는 방법은 아래와 같이 색을 파라미터화 하는 것이다. 

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

그런데 농부가 또 변심을 했다. 이번엔 무게가 150g 이상인 사과를 고르고 싶다고 한다.

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

색이 무게로 바뀌었다는 점만 빼면 코드 구조가 비슷하다. 또 중복이다! 이럴 순 없다.

나중에 농부가 빨갛고 무게가 150g 이상인 사과를 달라고 하면 어떻게 할 것인가?! 다른 요구사항이 더 추가되면 그 때는 어떻게 할 것인가?

 

이 문제를 해결하기 위해 닌자 동작 파라미터화가 나타났다. 한번 적용해보자.

빨갛다, 150g 이상이다, 등은 사과의 속성을 나타낸다.

그럼 빨갛고 150g 이상이라는 속성을 만족하게 하면 되는 것 아닌가?

 

전략 패턴을 적용해 속성을 만족한다 는 인터페이스를 만들어보자.

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

이제 우리가 원하는 빨갛고 150g 이상의 속성을 만족하는 ApplePredicate 클래스를 정의해보자.

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

우린 방금 하나의 동작을 정의했다! 이제 이걸 아래와 같이 파라미터화 하면 된다.

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

위와 같은 방식으로 어떠한 동작을 할 수 있는 코드 블록를 파라미터로 받아 우리가 원하는 동작을 수행하도록 하는 게 동작 파라미터화다.


기초는 끝났다. 여기서부턴 동작 파라미터화를 더 간결하게, 더 추상적으로 만들기 위한 얘기이다.

 

Q. 위의 경우엔 ApplePredicate 인터페이스를 구현하는 여러 클래스를 정의하고 인스턴스화 해야한다. 너무 번거롭고 시간 낭비 같다.

A. 그렇다. 시간 낭비가 될 수 있다. 로직과 관련 없는 코드가 추가되기도 한다. 이를 개선하기 위해 익명 클래스가 있다.

 

익명 클래스 anonymous class
이름이 없는 클래스로, 클래스 선언과 인스턴스화를 동시에 할 수 있어 즉석에서 필요한 구현을 할 수 있다.

 

예시로 빨간 사과를 고르는 ApplePredicate를 아래와 같이 쓸 수 있다.

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

filterApples 메소드의 파라미터로 들어가는 ApplePredicate를 즉석에서 구현하여 인스턴스화 하는 것이다.

 

Q. 익명 클래스보다 더 간결한 것을 원한다.

A. 그럴 줄 알았다. 나도 그랬기 때문이다. 그런 우릴 위해 람다 표현식이 있다.

람다 표현식에 대해선 나중에 다룰테니 예시만 보여주겠다.

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

 

Q. 사과 말고 다른 과일이나 물건도 고를 수 있게 하고 싶다.

A. 리스트 형식으로 추상화를 해보자!

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

public
 List<T> filter(List<T> list, Predicate<T> predicate) {

        List<T> result = new ArrayList<>();
        for ( T value : list ) {
                    if ( predicate.test(value) ) {   
                              result.add(value);
                     }
        }
        return result;
}

// 사용 예시
List<Apple> redApples =
        filter(inventory, (Apple apple) -> RED.equals(apple.getColor()));

List<Integer> evenNumbers =
       filter(numbers, (Integer i) -> i % 2 == 0);

 

참고

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