ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • CHAP2. 동작 파라미터화 코드 전달하기
    book/모던 자바 인 액션 2020. 7. 24. 21:47

    동작 파라미터화

    : 아직은 어떻게 실행할 것인지 결정하지 않은 코드 블록. 이 코드 블록은 나중에 프로그램 실행 중에 호출한다.

      즉, 자주 바뀌는 요구사항에 효과적으로 대응할 수 있다는 뜻

     

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

    1). 첫 번째 시도 : 녹색 사과 필터링

    private enum Color {
        GREEN,
        RED
    }
    
    private static List<Apple> filterGreenApples(List<Apple> apples) {
        List<Apple> result = new ArrayList<>();
        for(Apple apple: apples) {
            if(Color.GREEN.equals(apple.getColor())) {
                result.add(apple);
            }
        }
        return result;
    }

    위의 코드는 녹색 사과를 선택하도록 구현이 되어있지만

    빨간 사과도 선택하려고 하면 코드를 수정해야 한다.

     

    2). 두 번째 시도 : 색을 파라미터화

    private enum Color {
        GREEN,
        RED
    }
    
    //파라미터로 색깔이 들어오면 색깔 별로 탐색 가능
    private static List<Apple> filterApplesByColor(List<Apple> apples, Color color) {
        List<Apple> result = new ArrayList<>();
        for(Apple apple: apples) {
            if(color.equals(apple.getColor())) {
                result.add(apple);
            }
        }
        return result;
    }
    
    //사과를 무게별로 구분할 수 있도록 하는 메소드 추가
    private static List<Apple> filterApplesByWeight(List<Apple> apples, int weight) {
        List<Apple> result = new ArrayList<>();
        for(Apple apple: apples) {
            if(apple.getWeight() > weight) {
                result.add(apple);
            }
        }
        return result;
    }

    위의 코드는 색을 파라미터화할 수 있도록 메소드에 파라미터를 추가한 코드이다.

    그리고 만약 색 이외에도 사과를 무게별로 구분할 수 있도록 하기 위해서 메소드를 추가하였다.

    위 코드도 문제가 없는 코드지만 색과 무게를 구별하는 필터링 코드가 대부분 중복이 된다.

    이는 소프트웨어 공학의 DRY(Don't repeat yourself)원칙을 어기는 것이다.

     

    3). 세 번째 시도 : 가능한 모든 속성으로 필터링

    private static List<Apple> filterApples(List<Apple> apples, Color color, int weight, boolean flag) {
        List<Apple> result = new ArrayList<>();
        for(Apple apple: apples) {
            if((flag && apple.getColor().equals(color)) || 
            (!flag && apple.getWeight() > weight)) {
                result.add(apple);   
            }
        }
        return result;
    }
    
    public static void main(String[] args) {
        List<Apple> appleList = Arrays.asList(new Apple(80, Color.GREEN), 
                                                new Apple(120, Color.RED), 
                                                new Apple(155, Color.GREEN);
                                                
        final List<Apple> greenAppleList = filterApples(appleList, Color.GREEN, 0, true);
        final List<Apple> heavyAppleList = filterApples(appleList, null, 150, false);
    }

     모든 속성을 메소드 파라미터로 추가한 모습이다.

    바람직하지 못한 코드다

    flag라는 boolean 변수가 뭘 의미하는지 알 수 없고 앞으로 요구사항이 바뀐다면 어떻게 유연하게 대응할수도 없다.

     

    2. 동작 파라미터화

    private interface ApplePredicate {
        boolean test (Apple apple);
    } 
    
    private static class AppleHeavyWeightPredicate implements ApplePredicate {
        public boolean test(Apple apple) {
            return apple.getWeight() > 150;
        }
    }
    
    private static class AppleGreenColorPredicate implements ApplePredicate {
        public boolean test(Apple apple) {
            return Color.GREEN.equals(apple.getColor());
        }
    }

    참 또는 거짓을 반환하는 함수를 프레디케이트라고 한다.

    위와 같이 인터페이스과 인터페이스를 구현하는 클래스들을 선언한다.

    조건에 따라 filter 메소드가 다르게 동작할 것이라고 예상할 수 있는데 이를 전략 디자인 패턴이라고 한다.

     

    전략 디자인 패턴 ( Strategy Design Pattern )

    각 알고리즘(전략)을 캡슐화하는 알고리즘 패밀리를 정의해둔 다음에 런타임에 알고리즘을 선택하는 기법.

    위의 코드에서 ApplePredicate가 알고리즘 패밀리이고 AppleHeavyWeightPredicate, AppleGreenColorPredicate가 전략이다.

    filterApples 메소드에 ApplePredicate 객체를 받아 사과의 조건을 검사하도록 한다면 동작 파라미터화, 즉 메소드가 다양한 동작을 받아서 내부적으로 다양한 동작을 수행할 수 있게 된다.

     

     

    1). 네 번째 시도 : 추상적 조건으로 필터링

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

    첫 번째 코드에 비해 더 유연한 코드를 얻었고 동시에 가독성도 좋아졌을 뿐 아니라 사용하기도 쉬워졌다.

    이제 필요한 대로 다양한 ApplePredicate를 만들어서 filterApples 메소드로 전달할 수 있다.

    근데 조건마다 클래스를 구현해서 인스턴스화하는 과정이 조금은 불편할 수 있다.

     

    3. 복잡한 과정 간소화

    1). 익명 클래스 사용

    자바는 클래스의 선언과 인스턴스화를 동시에 수행할 수 있도록 익명 클래스라는 기법을 제공한다.

     

    익명 클래스

    자바의 지역 클래스와 비슷한 개념이다. 말 그대로 이름이 없는 클래스.

    익명 클래스를 이용하면 클래스 선언과 인스턴스화를 동시에 할 수 있다. 즉 즉석에서 필요한 구현을 만들어 사용할 수 있다.

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

    익명 클래스를 이용해서 ApplePredicate를 구현하는 객체를 만드는 방법으로 필터링 예제를 다시 구현했다.

    하지만 익명 클래스도 두 가지 문제점이 있다.

    1. 여전히 많은 공간을 차지한다. ( 코드가 장황함 )

    2. 사용에 익숙치 않다.

     

    2). 람다 표현식 사용

    List<Apple> redAppleList = filterApples(appleList, apple -> Color.RED.equals(apple.getColor()));

    자바 8의 기능인 람다를 사용하여 중복 코드를 줄이고 간결하게 정리하였다.

    'book > 모던 자바 인 액션' 카테고리의 다른 글

    CHAP5. 스트림 활용  (0) 2020.08.23
    CHAP4. 스트림 소개  (0) 2020.08.11
    CHAP3. 람다 표현식  (0) 2020.07.28
    CHAP1. 자바 8, 9, 10, 11 : 무슨 일이 일어나고 있는가?  (0) 2020.07.13
    JAVA8 공부 시작  (0) 2020.07.11
Designed by Tistory.