회고

20220621-22 TIL #인터페이스 #동적 파라미터화 #일급컬렉션

paran21 2022. 6. 22. 21:57

<모던 자바 인 액션>을 읽기 시작했다.

책 자체가 굉장히 잘 쓰여져 느낌을 받았고, 편집도 좋다.

 

아마 최근에 관심 주제랑도 맞닿아 있어서 더 그런 것 같은데, 왜 람다를 사용해야 하는지에 대해 설득력 있게 느껴지고 있다.

특히 동적 파라미터화로 메서드를 전달할 수 있다는 점이 재미있다.

 

1. 인터페이스를 활용하면 메서드를 변수로 받을 수 있다.

 

예를 들어 List<Apple>을 색깔로 필터할 수 있다.

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

 

그런데, 여기에 새로운 필터 조건을 추가한다고 하면 새로운 메서드를 만들어야 한다.

public static List<Apple> filterApplesUnderWeight(List<Apple> inventory, int weight) {
    List<Apple> result = new ArrayList<>();
    for (Apple apple : inventory) {
        if(apple.getWeight() < weight) {
            result.add(apple);
        }
    }
    return result;
}

이러한 필터 조건들을 인터페이스로 만들고, 각각의 구현체를 만들어서 전달할 수 있다.

public interface FilterApplePredicate {
    boolean test(Apple apple);
}
public class FilterAppleByColor implements FilterApplePredicate {

    @Override
    public boolean test(Apple apple) {
        return apple.getColor().equals(Color.GREEN);
    }
}
public class FilterAppleRedAndHeavyPredicate implements FilterApplePredicate {

    private final int WEIGHT = 50;

    @Override
    public boolean test(Apple apple) {
        return apple.getColor().equals(Color.RED) && apple.getWeight() > WEIGHT;
    }
}

그러면 하나의 메서드로 필터할 수 있다.

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

 

2. 여러 Apple들을 하나로 관리하기 위해 일급 컬렉션 Apples를 사용할 수 있다.

 

public class Apples {
    private final List<Apple> apples;

    public Apples(Apple... apple) {
        this.apples = new ArrayList<>();
        Collections.addAll(apples, apple);
    }
}

하나의 변수만 갖는 일급 컬렉션을 사용하면 여러가지 장점들이 언급되지만, 오늘 리펙토링하면서 느낀 장점은 모든 사과들을 빼먹지 않고 한번에 필터 메서드를 적용할 수 있다는 점이다.(https://jackjeong.tistory.com/107)

 

3. 스트림을 사용해서 코드를 더 간결하게 작성할 수 있다.

 

여기에 스트림을 사용하면 코드를 매우 간결히 줄일 수 있다!

public List<Apple> filterApples(FilterApplePredicate p) {
    return apples.stream()
            .filter(p::test)
            .collect(Collectors.toList());
}

 

 

이런식으로 Apple을 Apples라는 객체로 생성하고, 메서드를 사용하면 된다.

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.EnumSet;

class AppleTest {

    Apples apples;

    @BeforeEach
    void createApples() {
        apples = new Apples(new Apple(Color.GREEN, 20),
                new Apple(Color.RED, 60));
    }

    @Test
    void filter_apples_with_ApplePredicate() {
        Assertions.assertThat(
                apples.filterApples(new FilterAppleRedAndHeavyPredicate()).size())
                .isEqualTo(1);
    }

    @Test
    void print_apples_with_Predicate() {
        OutputStream out = new ByteArrayOutputStream();
        System.setOut(new PrintStream(out));
        apples.printApples(new PrintAppleColor());
        Assertions.assertThat(out).toString().contains(Color.GREEN.name());
    }

}