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

Junseo Kim·2021년 2월 22일

동작 파라미터화란?

아직 어떻게 실행할 것인지 결정하지 않은 코드 블록. 나중에 프로그램에서 호출해서 사용하는 것. 즉, 코드 블럭의 실행이 나중으로 미뤄진다. 요구 사항은 언제든 변화할 수 있기 때문에 이처럼 코드 블럭의 실행을 나중으로 미루면 효과적으로 대응할 수 있다.

메서드의 인자로 코드 블럭을 전달할 수 있기 때문에, 코드 블럭에 따라 메서드의 내부 동작이 결정되고, 이를 동작이 파라미터화 된다고한다.

변하는 요구사항에 대응

요구사항이 변하게 된다면 메서드의 동작도 변화되어야 한다. 하지만 요구사항이 변경될 때 마다 메소드에 파라미터를 추가하여 처리하는 방법에는 한계가 존재한다. 따라서 요구사항에 유연하게 대응할 수 있는 방법이 필요하다.

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

전략 패턴

요구 사항이 계속 변할 때, 인터페이스를 사용해서 해결하는 방법이 있다.

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

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

2) 요구사항에 맞는 인터페이스를 구현하기

// 무거운 사과만 선택하기
public class AppleHeavyWeightPredicate implements ApplePredicate {
    public boolean test(Apple apple) {
        return apple.getWeight() > 150;
    }
}
// 무거운 사과만 선택하기
public class AppleGreenColoePredicate implements ApplePredicate {
    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;
}

이런 것이 동작 파라미터화이다. 전달한 전략에 의해 메서드의 동작이 결정된 것이다. 메서드의 인자로 동작을 넘겨주고, 그 넘겨준 동작에 의해 메서드의 실제 동작이 결정되므로 동작이 파라미터화 된 것이다.

위의 방법을 좀 더 개선할 수 있다. 현재는 동작마다 클래스를 생성해서 구현한 후 인스턴스화 하고 있다.

익명 클래스

새로운 클래스를 생성하는 대신 익명 클래스를 사용할 수도 있다. 익명 클래스는 이름이 없는 클래스로, 클래스 선언과 인스턴스화를 동시에 할 수 있다.

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

하지만 익명 클래스의 사용은 공간을 많이 차지한다는 단점이 있다. 또 가독성이 좋지않다.

람다 표현식

익명 클래스의 단점을 보완하기 위해 람다식을 사용할 수 있다.

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

리스트 형식으로 추상화

만약 Apple 뿐만 아니라 다른 타입에도 위의 filter 메서드를 사용하고 싶다면 다른 메서드로 생성해야할까?

filter 메서드를 특정 타입에 국한하지 않으면 된다.

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

메서드를 이렇게 추상화하면 아래와 같이 Apple 뿐만아니라 다른 타입으로도 얼마든지 사용할 수 있다.

List<Apple> redApples = filter(inventory, (Apple apple) -> RED.equals(apple.getColor()));
List<Integer> evenNumbers = filter(numbers, (Integer i) -> i % 2 == 0);

동작 파라미터 정리

1) 동작을 캡슐화한다.
2) 캡슐화한 동작을 메서드로 전달한다. - 파라미터화

0개의 댓글