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

Junseo Kim·2021년 2월 22일
1

동작 파라미터화란?

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

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

변하는 요구사항에 대응

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

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

전략 패턴

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

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개의 댓글