럿고의 개발 노트

동작 파라미터화 본문

Java Note/모던 자바 인 액션(Modern Java in Action)

동작 파라미터화

KimSeYun 2020. 3. 11. 00:14

동작 파라미터화

동작 파라미터화란?

  • 메서드 내부적으로 다양한 동작을 수행할 수 있도록 코드를 메서드 인수로 전달합니다.
  • 자주 바뀌는 요구사항에 대해 효과적으로 대응 할 수 있으며, 아직 어떻게 실행할 것인지 결정하지 않은 코드 블럭을 의미합니다.
  • 이 코드 블럭은 나중에 프로그램에서 호출되며 코드 블록의 실행은 나중으로 미뤄집니다. 결과적으로 코드 블록에 따라서 메서드의 동작이 파라미터화가 됩니다.

예제

잘못된 코드

사과의 클래스 생성

enum Color {
    RED, GREEN
}
public class Apple {
    private int weight;
    private Color color;

    public Apple(int weight, Color color) {
        this.weight = weight;
        this.color = color;
    }

    public int getWeight() {
        return weight;
    }

    public Color getColor() {
        return color;
    }
}

for-each를 이용한 녹색사과, 빨간사과만 필터링 하는 코드

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class FilterApples {
    private final static List<Apple> inventory = Arrays.asList(
            new Apple(80, Color.RED),
            new Apple(155, Color.GREEN),
            new Apple(120, Color.GREEN)
    );

    public static List<Apple> filterGreenApples(List<Apple> inventory){
        List<Apple> result = new ArrayList<>();
        for (Apple apple : inventory){
            if(Color.GREEN.equals(apple.getColor())){
                result.add(apple);
            }
        }
        return result;
    }

    public static List<Apple> filterRedApples(List<Apple> inventory){
        List<Apple> result = new ArrayList<>();
        for (Apple apple : inventory){
            if(Color.RED.equals(apple.getColor())){
                result.add(apple);
            }
        }
        return result;
    }

}
  • 빨간사과만 필터링하는 예제와 녹색사과만 필터링하는 예제에 중복코드가 굉장히 많아 보입니다.
  • 그 코드를 추상화 하는 것을 추천한다.

Color를 파라미터화 하기 및 무게 필터링 메서드도 생성

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class FilterApples {
    private final static List<Apple> inventory = Arrays.asList(
            new Apple(80, Color.RED),
            new Apple(155, Color.GREEN),
            new Apple(120, Color.GREEN)
    );

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

    public static void main(String[] args) {
        List<Apple> greenApples = filterAppleByColor(inventory, Color.GREEN);
        List<Apple> redApples = filterAppleByColor(inventory, Color.RED);
        List<Apple> heavyApples = filterAppleByWeight(inventory, 150);
    }
}
  • 위의 코드처럼 Color를 인수로 받아서 한 메서드로 같이 했다.
  • 또한 이번에 하면서 무개를 가지고 비교하는 코드도 작성하였는데, 두개의 메서드가 중복코드가 많다 보니 DRY(Don't Repeat Yourself)의 원칙을 어기게 되었다.

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

  • 그렇다면 모든 필터링할 속성을 인수로 받으면 되지 않을까라는 생각을 할 수도 있다.
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class FilterApples {
    private final static List<Apple> inventory = Arrays.asList(
            new Apple(80, Color.RED),
            new Apple(155, Color.GREEN),
            new Apple(120, Color.GREEN)
    );

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

    public static void main(String[] args) {
        List<Apple> greenApples2 = filterApples(inventory, Color.GREEN, 0, true);
        List<Apple> heavyApples2 = filterApples(inventory, null, 150, false);
    }
}
  • 딱봐도 정신이 없고, 요구사항이 변하게 되었을때도 유연하게 대응할 수도 없게 될 것이다.
  • 이런 경우에 동작 파라미터화를 이용하면 훨씬 더 유연하고 깔끔한 코드를 짤수 있을 것이다.

동작 파라미터화를 이용한 코드

  • 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 Color.GREEN.equals(apple.getColor());
    }
}
  • 이런식으로 test메서드에 들어갈 조건을 각 클래스로 정의해주면 되는데 이런 것을 전략이라고 이야기 한다.
  • 디자인 패턴에서는 전략 패턴이라는 것이 있는데, 각 전력들을 캡슐화하는 전략 패밀리를 정의해 둔 다음에 런타임에 전략을 선택하는 기법이다.
  • 이와 관련된 글은 한번 포스팅을 한 적이 있다. 아래에 링크를 남겨놓겠습니다.
  • 전략패턴 & 인터페이스 추상화
  • 그러나 위에서 구현한것 처럼 각각의 전략들을 클래스화를 한 다는 것은 번거롭고 시간낭비가 엄청날 것이다.
  • 자바는 클래스 선언과 인스턴스화를 동시에 할 수 있는 익명 클래스를 지원한다.

익명클래스를 사용

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class FilterApples {
    private final static List<Apple> inventory = Arrays.asList(
            new Apple(80, Color.RED),
            new Apple(155, Color.GREEN),
            new Apple(120, Color.GREEN)
    );

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

    public static List<Apple> correctFilterApples(List<Apple> inventory, ApplePredicate applePredicate){
        List<Apple> result = new ArrayList<>();
        for (Apple apple : inventory){
            if(applePredicate.test(apple)){
                result.add(apple);
            }
        }
        return result;
    }
}
  • 그러나 익명 클래스는 많은 공간을 차지하게 되어 클래스 내에서 코드가 복잡해질수 있다는 단점을 가지고 있다.
  • 이걸 해결하기 위해서 자바8에서는 람다를 지원한다.

람다표현식 사용

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class FilterApples {
    private final static List<Apple> inventory = Arrays.asList(
            new Apple(80, Color.RED),
            new Apple(155, Color.GREEN),
            new Apple(120, Color.GREEN)
    );

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

    public static List<Apple> correctFilterApples(List<Apple> inventory, ApplePredicate applePredicate){
        List<Apple> result = new ArrayList<>();
        for (Apple apple : inventory){
            if(applePredicate.test(apple)){
                result.add(apple);
            }
        }
        return result;
    }
}
  • 이렇게 한다면 코드가 훨씬 간결해진 것이 느껴질 것이다.

정리

  • 동작 파라미터화에서는 메서드 내부적으로 다양한 동작을 수행할 수 있도록 코드를 메서드 인수로 전달한다.
  • 동작 파라미터화를 이용하면 변화하는 요구사항에 더 잘 대응할 수 있는 코드를 구현할 수 있으며 나중에 엔지니어링 비용을 줄 일 수 있다.
  • 코드 전달 기법을 이용한다면 동작을 메서드로의 인수로 전달 할 수 있으나, 클래스들이 많이 지거나 익명클래스로 구현하게 된다면 코드가 장황해지는 단점이 존재했다. 해결 하기 위해서는 람다를 이용하면 훨씬 더 깔끔한 코드를 작성할 수 있을 것이다.
Comments