ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Part1] Java8 In action - Chapter2 - 1
    Java8 In Action 2022. 7. 26. 17:34
    반응형

    해당 내용은 Java8 In Action 책을 요약 및 정리한 내용입니다.

    좀 더 자세히 알고 싶으신 분들은 책을 사서 읽어보심을 추천드립니다.!

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

    Entity

    AppleEntity.java

    package Part1.Chapter2.Chapter2_2_1.entity;
    
    public class AppleEntity {
    
        private String color;
        private int weight;
    
        public AppleEntity() {}
    
        public AppleEntity(String color, int weight) {
            this.color = color;
            this.weight = weight;
        }
    
        public String getColor() {
            return color;
        }
        public void setColor(String color) {
            this.color = color;
        }
        public int getWeight() {
            return weight;
        }
        public void setWeight(int weight) {
            this.weight = weight;
        }
    
    }

    Main

    package Part1.Chapter2.Chapter2_2_1;
    
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;
    
    import Part1.Chapter2.Chapter2_2_1.entity.AppleEntity;
    
    /*
     * 2.1 변화하는 요구사항에 대응하기.
     * 농부가 재고목록 조사를 쉽게 할 수 있는 어플리케이션이 있다고 가정하자.
     */
    public class Main_2_1 {
    
        /*
         * 2.1.1 첫 번재 시도 : 녹색 사과 필터링.
         * 농장 재고목록 어플리케이션 녹색 사과만 필터링 하는 기능을 추가한다.
         * (해당 코드는 갑님의 변심이 생기기 전에 쓰던 코드 이므로 사용하는 곳이 없다.)
         */
        public static List<AppleEntity> filterGreenApples(List<AppleEntity> inventory) {
            List<AppleEntity> result = new ArrayList<>();
    
            for(AppleEntity apple : inventory) {
                if("green".equals(apple.getColor()) ) {
                    result.add(apple);
                }
            }
    
            return result;
        }
    
        /*
         * 2.1.2 두 번재 시도 : 색을 파라미터화
         * 그런데 우리 갑님께서의 변심으로 녹색 사과 말고 빨간 사과도 필터링하고 싶어 하셨다.
         * 다양항 색의 사과 필터링을 위해 아래와 같이 메서드를 만들었다.
         */
        public static List<AppleEntity> filterAppleByColor(List<AppleEntity> inventory, String color) {
            List<AppleEntity> result = new ArrayList<>();
    
            for(AppleEntity apple : inventory) {
                if(apple.getColor().equals(color)) {
                    result.add(apple);
                }
            }
    
            return result;
        }
    
        /*
         * 갑님의 계속된 요구사항을 들어보니 색과 마찬가지로 무게 기준도 얼마든지 바뀔 수 있을거 같아
         * 아래와 같이 다양한 무게에 대응할 수 있도록 무게 정보 파라미터도 추가한 메서드도 만들었다.
         * 
         * 아래 메서드가 좋은 해결책이라 할 수 있지만 구현 코드를 보면 목록을 검색하고, 각 사과에 
         * 필터링 조건을 적용하는 부분의 코드가 색 필터링 코드와 대부분 중복된다는 사실을 확인했다.
         * 이는 소프트웨어 공학의 DRY(don't repeat yourself - 같은 것을 반복하지 말 것)원칙을 어기는 것이다.
         * 
         * 탐색 과정을 고쳐서 성능을 개선하려 한다면? 그러면 코드 한 줄이 아닌 메서드 전체 구현을 고쳐야 한다.
         * 즉, 엔지니어링적으로 비싼 대가를 치러야 한다.
         */
        public static List<AppleEntity> filterAppleByWeight(List<AppleEntity> inventory, int weight) {
            List<AppleEntity> result = new ArrayList<>();
    
            for(AppleEntity apple : inventory) {
                if(apple.getWeight() > weight) {
                    result.add(apple);
                }
            }
    
            return result;
        }
    
        /*
         * 비싼 대가를 치루지 않기 위해 색과 무게를 filter라는 메서드로 합치는 방법을 선택 했다.
         * 해당 메서드는 색이나 무게 중 어떤 것을 기준으로 필터링할지 가라키는 플래그를 추가 했다.
         * 
         * 그러나 형편 없는 코드이다..
         * 대체 true와 false가 뭘 의미 하는지 알 수 없을 뿐더러 여기서 좀 더 다양한 요구사항이 생겼을시에
         * 여러 중복된 필터 메서드를 만들거나 모든 것을 처리하는 거대한 하나의 필터 메서드를 구현해야 한다.
         * 지금까지 사용한 문자열, 정수, 불린 등의 값으로 filterApples 메서드를 파라미터화 했다면 문제가 잘 정의되어 있는 상황에서의
         * 이 방법이 잘 동작할 수 있다.
         */
        public static List<AppleEntity> filterApples(List<AppleEntity> inventory, String color, int weight, boolean flag) {
            List<AppleEntity> result = new ArrayList<>();
    
            for(AppleEntity apple : inventory) {
                if((flag && apple.getColor().equals(color)) 
                    || (!flag && apple.getWeight() > weight)) {
                    result.add(apple);
                }
            }
    
            return result;
        }    
    
        public static void main(String[] args) {        
            List<AppleEntity> inventory = Arrays.asList(new AppleEntity("green", 100)
                , new AppleEntity("red", 120)
                , new AppleEntity("green", 150));
    
            /*
             * 2.1.2의 요구사항에 맞춰 만들었기 때문에 갑님도 만족해 할것이다.(애증의 갑님..)
             */
            // 색깔 필터링.
            filterAppleByColor(inventory, "red").stream()
                .forEach((apple) -> System.out.println("[filterAppleByColor] color : " + apple.getColor() + ", weight : " + apple.getWeight()) );
    
            filterAppleByColor(inventory, "green").stream()
                .forEach((apple) -> System.out.println("[filterAppleByColor] color : " + apple.getColor() + ", weight : " + apple.getWeight()) );
    
            // 무게 필터링.
            filterAppleByWeight(inventory, 90).stream()
                .forEach((apple) -> System.out.println("[filterAppleByWeight] weight = 90, color : " + apple.getColor() + ", weight : " + apple.getWeight()) );
    
            filterAppleByWeight(inventory, 100).stream()
                .forEach((apple) -> System.out.println("[filterAppleByWeight] weight = 100, color : " + apple.getColor() + ", weight : " + apple.getWeight()) );
    
            // 색깔/무게 필터링.
            filterApples(inventory, "", 110, false).stream()
                .forEach((apple) -> System.out.println("[filterApples] weight filter = 110, color : " + apple.getColor() + ", weight : " + apple.getWeight()) );
    
            filterApples(inventory, "green", 0, true).stream()
                .forEach((apple) -> System.out.println("[filterApples] color filter = " + apple.getColor() + ", color : " + apple.getColor() + ", weight : " + apple.getWeight()) );
        }
    
    }

    2.2 동작 파라미터화

    Entity

    AppleEntity.java

    package Part1.Chapter2.Chapter2_2_2.entity;
    
    public class AppleEntity {
    
        private String color;
        private int weight;
    
        public AppleEntity() {}
    
        public AppleEntity(String color, int weight) {
            this.color = color;
            this.weight = weight;
        }
    
        public String getColor() {
            return color;
        }
        public void setColor(String color) {
            this.color = color;
        }
        public int getWeight() {
            return weight;
        }
        public void setWeight(int weight) {
            this.weight = weight;
        }    
    
    }

    Interface

    ApplePredicate.java

    package Part1.Chapter2.Chapter2_2_2.filters.inter;
    
    import Part1.Chapter2.Chapter2_2_2.entity.AppleEntity;
    
    public interface ApplePredicate {
        boolean test(AppleEntity appleEntity);
    }

    Implements

    AppleHeavyColorPredicate.java

    package Part1.Chapter2.Chapter2_2_2.filters.impl;
    
    import Part1.Chapter2.Chapter2_2_2.entity.AppleEntity;
    import Part1.Chapter2.Chapter2_2_2.filters.inter.ApplePredicate;
    
    public class AppleHeavyColorPredicate implements ApplePredicate {
    
        @Override
        public boolean test(AppleEntity appleEntity) {
            return "green".equals(appleEntity.getColor());
        }
    
    }

    AppleHeavyWeightPredicate.java

    package Part1.Chapter2.Chapter2_2_2.filters.impl;
    
    import Part1.Chapter2.Chapter2_2_2.entity.AppleEntity;
    import Part1.Chapter2.Chapter2_2_2.filters.inter.ApplePredicate;
    
    public class AppleHeavyWeightPredicate implements ApplePredicate {
    
        @Override
        public boolean test(AppleEntity appleEntity) {
            return appleEntity.getWeight() > 150;
        }
    
    }

    Main

    package Part1.Chapter2.Chapter2_2_2;
    
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;
    
    import Part1.Chapter2.Chapter2_2_2.entity.AppleEntity;
    import Part1.Chapter2.Chapter2_2_2.filters.impl.AppleHeavyColorPredicate;
    import Part1.Chapter2.Chapter2_2_2.filters.impl.AppleHeavyWeightPredicate;
    import Part1.Chapter2.Chapter2_2_2.filters.inter.ApplePredicate;
    
    /*
     * 2.2 동작 파라미터화
     * 2.1절에서 파라미터를 추가하는 방법이 아닌 변화하는 요구사항에 좀 더 유연하게 대응할 수 있게
     * 전체을 보면 우리의 선택 조건은 
     *         사과의 어떤 속성에 기초해서 불린값을 반환(예를 들어 사과가 녹색인가? 150그램 이상인가?)하는 방법
     * 을 결정 할 수 있다.
     * 이와 같은 동작을 프레디케이트라고 한다.
     * 
     * 먼저 선택 조건을 결정하는 인터페이스를 만들자.
     * (Part1.Chapter2.Chapter2_2_2.filters.inter.ApplePredicate)
     * 
     * 그리고 
     *         - 무거운 사과만 선택
     *         - 녹색 사과만 선택
     * 과 같은 필터 기능을 위해 해당 인터페이스의 구현체를 만든다.
     *         Part1.Chapter2.Chapter2_2_2.filters.impl.AppleHeavyWeightPredicate
     *             무거운 사과만 선택 하는 필터.             
     *        Part1.Chapter2.Chapter2_2_2.filters.impl.AppleHeavyColorPredicate
     *            녹색 사과만 선택하는 필터.
     * 
     * 위 조건에 따라 filter 메서드가 다르게 동작 하는데 이를 전략 디자인 패턴(strategy design pattern)이라고 부른다.
     * 전략 디자인 패턴은 
     *         각 알고리즘을 캡슐화하는 알고리즘 패밀리를 정의해둔 다음에 런타임에 알고리즘을 선택하는 기법이다.
     * 우리 예제에서는 ApplePredicate가 알고리즘 패밀리고 AppleHeavyWeightPredicate, AppleHeavyColorPredicate가 전략이다.
     * */
    public class Main_2_2 {
    
        /*
         * 2.2.1 네 번째 시도 : 추상적 조건으로 필터링(이 정도면 갑님한테 돈을 더 요구 할수도 있을거 같다..)
         */
        public static List<AppleEntity> filterApples(List<AppleEntity> inventory, ApplePredicate p) {
            List<AppleEntity> result = new ArrayList<>();
    
            for(AppleEntity apple : inventory) {
                /*
                 * 해당 예제에서 가장 중요한 구현은 test 메서드 이다.
                 * filterApples 메서드의 새로운 동작을 정의하는 것이기 때문이다.
                 * 메서드는 객체만 인수로 받으므로 test 메서드를 ApplePredicate 객체로 감싸서 전달해야 한다.
                 * test 메서드를 구현하는 객체를 이용해서 불린 표현식 등을 전달할 수 있으므로 
                 * 이는 '코드를 전달'할 수 있는것이나 다름없다.
                 */
                if(p.test(apple)) {
                    result.add(apple);
                }
            }
    
            return result;
        }
    
        public static void main(String[] args) {
            List<AppleEntity> inventory = Arrays.asList(new AppleEntity("green", 100)
                    , new AppleEntity("red", 120)
                    , new AppleEntity("green", 150));
    
            /*
             * 이렇게 동작 파라미터화, 즉 메서드가 다양한 동작(또는 전략)을 받아서 내부적으로 다양한 동작을 수행할 수 있다.
             * 이렇게 함으로써 filterApples 메서드 내부에서
             *         컬렉션을 반복하는 로직과 컬렉션의 각 요소에 적용할 동작을(예제에서는 프레디케이트)분리
             * 한다는 점에서 소프트웨어 엔지니어링적으로 큰 이득을 얻는다.
             */
            filterApples(inventory, new AppleHeavyColorPredicate()).stream()
                .forEach((AppleEntity apple) -> System.out.println("[AppleHeavyColorPredicate] color : " + apple.getColor() + ", weight : " + apple.getWeight()) );
    
            filterApples(inventory, new AppleHeavyWeightPredicate()).stream()
                .forEach((AppleEntity apple) -> System.out.println("[AppleHeavyWeightPredicate] color : " + apple.getColor() + ", weight : " + apple.getWeight()) );
        }
    
    }

    Quiz

    Interface

    ApplePrintPredicate.java

    package Part1.Chapter2.Chapter2_2_2.Quiz.print.inter;
    
    import Part1.Chapter2.Chapter2_2_2.entity.AppleEntity;
    
    public interface ApplePrintPredicate {
        String print(AppleEntity apple);
    }

    Implements

    AppleFancyPrint.java

    package Part1.Chapter2.Chapter2_2_2.Quiz.print.impl;
    
    import Part1.Chapter2.Chapter2_2_2.Quiz.print.inter.ApplePrintPredicate;
    import Part1.Chapter2.Chapter2_2_2.entity.AppleEntity;
    
    public class AppleFancyPrint implements ApplePrintPredicate {
        int checkWeight = 0;
    
        public AppleFancyPrint(int checkWeight) {
            this.checkWeight = checkWeight;
        }
    
        @Override
        public String print(AppleEntity apple) {
            String printStr = apple.getWeight() > checkWeight ? "heavy" : "light";
    
            return "[Check Weight = " + this.checkWeight + ", Apple Weight = " + apple.getWeight() + "] " 
                    + "A " + printStr + " " + apple.getColor() + " apple";
        }
    
    }

    AppleSimplePrint.java

    package Part1.Chapter2.Chapter2_2_2.Quiz.print.impl;
    
    import Part1.Chapter2.Chapter2_2_2.Quiz.print.inter.ApplePrintPredicate;
    import Part1.Chapter2.Chapter2_2_2.entity.AppleEntity;
    
    public class AppleSimplePrint implements ApplePrintPredicate {
    
        @Override
        public String print(AppleEntity apple) {        
            return "An " + apple.getColor() + " apple of " + apple.getWeight() + "g";
        }
    }

    Main

    package Part1.Chapter2.Chapter2_2_2.Quiz;
    
    import java.util.Arrays;
    import java.util.List;
    
    import Part1.Chapter2.Chapter2_2_2.Quiz.print.impl.AppleFancyPrint;
    import Part1.Chapter2.Chapter2_2_2.Quiz.print.impl.AppleSimplePrint;
    import Part1.Chapter2.Chapter2_2_2.Quiz.print.inter.ApplePrintPredicate;
    import Part1.Chapter2.Chapter2_2_2.entity.AppleEntity;
    
    /*
     * 퀴즈 2-1
     * 유연한 prettyPrintApple 메서드 구현하기.
     * 
     * 사과 리스트를 인수로 받아 다양한 방법으로 문자열을 생성(커스터마이즈된 다양한 toString 메서드와 같이)
     * 할 수 있도록 파라미터화된 prettyPrintApple 메서드를 구현하시오.
     * 아래에 조건을 충족하시오.
     *         각각의 사과 무게를 출력하도록 지시할 수 있다.
     *         각각의 사과가 무거운지, 가벼운지 출력하도록 지시할 수 있다.
     */
    public class Quiz2_1 {
    
        public static void prettyPrintApple(List<AppleEntity> inventory, ApplePrintPredicate p) {
            for(AppleEntity apple : inventory) {
                System.out.println(p.print(apple));
            }
        }
    
        public static void main(String[] args) {
            List<AppleEntity> inventory = Arrays.asList(new AppleEntity("green", 90)
                    , new AppleEntity("red", 120)
                    , new AppleEntity("green", 150));
    
            prettyPrintApple(inventory, new AppleSimplePrint());
            prettyPrintApple(inventory, new AppleFancyPrint(10));
            prettyPrintApple(inventory, new ApplePrintPredicate() {            
                @Override
                public String print(AppleEntity apple) {
                    return "[Anonymous Class] apple Color : " + apple.getColor()
                        + ", apple weight : " + apple.getWeight();
                }
            });
        }
    }

    동작 파라미터화 코드 전달하기

    • 동작 파라미터화(behavior parameterization)란 아직은 어떻게 실행할 것인지 결정하지 않은 코드 블록을 의미.

    • 이 코드 블록은 나중에 프로그램에서 호출한다. 즉, 코드 블록의 실행은 나중으로 미뤄진다. 예로 컬렉션을 처리할 때 다음과 같은 메서드를 구현한다고 가정하자.

      • 리스트의 모든 요소에 '어떤 동작'을 수행할 수 있음.
      • 리스트 관련 작업을 끝낸 다음에 '어떤 다른 동작'을 수행할 수 있음.
      • 에러가 발생하면 '정해진 어떤 다른 동작'을 수행할 수 있음

      같이 동작 파라미터화로 위 따옴표 영역에 다양한 기능을 수행할 수 있다.

    요약

    • 동작 파라미터화에서는 메서드 내부적으로 다양한 동작을 수행할 수 있도록 코드를 메서드 인수로 전달한다.
    • 동작 파라미터화를 이용하면 변화하는 요구사항에 더 잘 대응할 수 있는 코드를 구현할 수 있으며 나중에 엔지니어링 비용을 줄일 수 있다.
    • 코드 전달 기법을 이용하면 동작을 메서드의 인수로 전달할 수 있다. 익명 클래스로도 어느 정도 코드를 깔끔하게 만들 수 있지만 자바 8에서는 인터페이스를 상속받아 여러 클래스를 구현해야 하는 수고를 없앨 수 있는 방법을 제공한다.
    • 자바 API의 많은 메서드는 정렬, 스레드, GUI 처리등을 포함한 다양한 동작으로 파라미터화할 수 있다.
    반응형

    댓글

Designed by Tistory.