본문 바로가기

모던자바인액션

[Part1] Chapter2. 동작 파라미터화 코드 전달하기

0. 목적

변화하는 요구사항에 유연하게 대응할 수 있는 코드를 구현하자.

- 비용은 최소화

- 구현은 쉽게

- 유지보수가 쉽게

 

동작(Behavior) 파라미터화

메서드가 다양한 '동작'을 받아서 내부적으로 다양한 동작을 수행할 수 있다.

말 그대로 동작이 파라미터로 사용된다.

 

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

🧑‍🌾 : 나는 녹색 사과만 보고싶어.

1.1. 녹색 사과 필터링

public enum Color {
    RED,
    GREEN
}
@Data
@AllArgsConstructor
public class Apple {
    public Color color;
}
public class 녹색사과필터링 {
    public static List<Apple> filterGreenApplesList(List<Apple> inventory){
        List<Apple> result = new ArrayList<>();
        for (Apple apple : inventory) {
            if (Color.GREEN.equals(apple.getColor())) {
                result.add(apple);
            }
        }
        return result;
    }
}

 

🧑‍🌾 : 빨간 사과도 볼 수 있는 방법은 없을까 ?

1.2. 색을 파라미터화

public class 색을파라미터화 {
    public static List<Apple> filterGreenApplesList(List<Apple> inventory, Color color){
        List<Apple> result = new ArrayList<>();
        for (Apple apple : inventory) {
            // 파라미터로 받은 color 로 비교
            if (apple.getColor().equals(color)) {
                result.add(apple);
            }
        }
        return result;
    }
}

 

🧑‍🌾 : 색 이외에도 가벼운 사과와 무거운 사과로 구분하고 싶어. 150그램 이상이면 무거운 사과야.

1.3. 속성 필터링

public class 가능한_모든_속성으로필터링 {
    public static List<Apple> filterGreenApplesList(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;
    }
}

 

가독성이 떨어지고, 필터링 조건이 다시 달라졌을 때 대응하기 힘들다.

 

2. 동작 파라미터화

🗣 그저 값 파라미터를 추가하는 것이 아닌, 좀 더 유연하게 대응할 수 있는 방법은 없을까 ?

 

public interface ApplePredicate {
    boolean test(Apple apple);
}
public class 추상조건으로_필터링 {
    public static List<Apple> filterApples(List inventory, ApplePredicate p){
        List<Apple> result = new ArrayList<>();
        for(Apple apple : inventory){
        // 프레디케이트 객체로 사과 검사조건을 캡슐화했다.
            if(p.test(apple)){
                result.add(apple);
            }
        }
        return result;
    }
}

class AppleHeavyPredicate implements ApplePredicate{
    public boolean test(Apple apple) {
        //무게 비교
        return false;
    }
}


class AppleColorPredicate implements ApplePredicate{
    public boolean test(Apple apple) {
        //색상 비교
        return false;
    }
}

public class FilteringApples {
    public static void main(String[] args) {
        List<Apple> inventory = Arrays.aslist(new Apple(80, GREEN),
                                                new Apple(155, GREEN),
                                                new Apple(120, RED));
        List<Apple> heavyApples = filterApples(inventory, new AppleHeavyWeightPredicate());
        List<Apple> greenApples = filterApples(inventory, new AppleGreenColorPredicate ());
}

filterApples() 메서드의 동작을 파라미터화 했다 !

 

🗣 새로운 동작을 하기 위해서는 인터페이스를 모두 구현한 클래스를 정의하며, 인스턴스화 해야해. 번거롭고 시간낭비야 !

3. 익명 클래스

이름이 없는 클래스. 클래스 선언과 인스턴스화를 동시에 할 수 있다.

즉, 즉석에서 필요한 구현을 만들어 사용할 수 있다.

* 인스턴스화 ? 인스턴스라는 것이 어떤 클래스로부터 만들어진 객체를 의미하기 때문에 / 클래스로부터 객체화하는 것.

public class 익명클래스 {

    public static List<Apple> filterApplesList(List<Apple> inventory,
        ApplePredicate applePredicate) {
        List<Apple> result = new ArrayList<>();
        for (Apple apple : inventory) {
            if (applePredicate.test(apple)) {
                result.add(apple);
            }
        }
        return result;
    }

    // 익명클래스 구현
    public void run() {
        List<Apple> inventory = List.of(
            new Apple(Color.GREEN, 50),
            new Apple(Color.RED, 111),
            new Apple(Color.GREEN, 13)
        );

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

 

4. 람다 표현식 

public class 람다표현식 {

    public static List<Apple> filterApplesList(List<Apple> inventory,
        ApplePredicate applePredicate) {
        List<Apple> result = new ArrayList<>();
        for (Apple apple : inventory) {
            if (applePredicate.test(apple)) {
                result.add(apple);
            }
        }
        return result;
    }

    // 익명클래스 구현
    public void run() {
        List<Apple> inventory = List.of(
            new Apple(Color.GREEN, 50),
            new Apple(Color.RED, 111),
            new Apple(Color.GREEN, 13)
        );

        //람다로 깔끔해짐
        List<Apple> result = filterApplesList(inventory, (Apple apple) -> Color.RED.equals(apple.getColor()));
    }
}

 

5. 리스트 형식으로 추상화

public class 리스트형식으로_추상화 {

    public static <T> List<T> filterList(List<T> inventory,
        Predicate<T> predicate) {
        List<T> result = new ArrayList<>();
        for (T e : inventory) {
            if (predicate.test(e)) {
                result.add(e);
            }
        }
        return result;
    }

    public void run() {
        List<Apple> inventory = List.of(
            new Apple(Color.GREEN, 50),
            new Apple(Color.RED, 111),
            new Apple(Color.GREEN, 13)
        );

        List<Integer> numbers = List.of(1, 2, 3, 4);
        // 제네릭 추상화
        List<Apple> result = filterList(inventory, (Apple apple) -> Color.RED.equals(apple.getColor()));
        List<Integer> numberList = filterList(numbers, (Integer i ) -> i%2 == 0);
    }
}

 

6. 결론

- 동작 파라미터화에서는 메서드 내부적으로 다양한 동작을 수행할 수 있도록 코드를 메서드 인수로 전달한다.

- 변화하는 요구사항에 더 잘 대응할 수 있는 코드를 구현할 수 있다.

- 코드 전달기법을 사용하여 동작을 메서드의 인수로 전달할 수 있다.