ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Part3] Java8 In action - Chapter8 - 1
    Java8 In Action 2022. 8. 3. 19:24
    반응형

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

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

    8.1 가독성과 유연성을 개선하는 리팩토링

    Entity

    Dish.java

    package Part3.Chapter8.Chapter8_8_1.entity;
    
    public class Dish {
        private final String name;
        private final boolean vegetarian;
        private final int calories;
        private Type type;
    
        public Dish(String name, boolean vegetarian, int calories, Type type) {    
            this.name = name;
            this.vegetarian = vegetarian;
            this.calories = calories;
            this.type = type;
        }
    
        public String getName() {
            return this.name;
        }
    
        public int getCalories() {
            return this.calories;
        }
    
        public Type getType() {
            return this.type;
        }
    
        public boolean isVegetarian() {
            return vegetarian;
        }
    
        public CaloricLevel getCaloricLevel() {
            if(this.getCalories() <= 400) {
                return Dish.CaloricLevel.DIET;
            } else if(this.getCalories() <= 700) {
                return Dish.CaloricLevel.NORMAL; 
            } else {
                return Dish.CaloricLevel.FAT;
            }
        }
    
        @Override
        public String toString() {
            return this.name;
        }
    
        public enum Type {
            MEAT, FISH, OTHER
        }
    
        public enum CaloricLevel {
            DIET, NORMAL, FAT
        }
    }

    Process

    Task.java

    package Part3.Chapter8.Chapter8_8_1.task;
    
    public interface Task {
        public void execute();
    
        public static void doSomthing(Runnable r) {
            r.run();
        }
    
        public static void doSomthing(Task a) {
            a.execute();
        }
    }

    Main

    package Part3.Chapter8.Chapter8_8_1;
    
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;
    import java.util.Map;
    import java.util.stream.Collectors;
    
    import Part3.Chapter8.Chapter8_8_1.entity.Dish;
    import Part3.Chapter8.Chapter8_8_1.task.Task;
    
    /*
     * 8.1 가독성과 유연성을 개선하는 리팩토링
     * 
     * 람다 표현식은 익명 클래스보다 코드를 좀 더 간결하게 만든다.
     * 동작 파라미터화의 형식을 지원하므로 람다 표현식을 이용한 코드는 더 큰 유연성을 갖출 수 있다.
     * 즉, 람다 표현식을 이용한 코드는 다양한 요구사항 변화에 대응할 수 있도록 파라미터화한다.
     */
    public class Main_8_1 {
    
        public static void main(String[] args) {
            List<Dish> menu = Arrays.asList(
                new Dish("pork", false, 800, Dish.Type.MEAT)    
                , new Dish("beef", false, 700, Dish.Type.MEAT)                
                , new Dish("chicken", false, 400, Dish.Type.MEAT)
                , new Dish("french fries", true, 530, Dish.Type.OTHER)
                , new Dish("rice", true, 350, Dish.Type.OTHER)
                , new Dish("season fruit", true, 120, Dish.Type.OTHER)
                , new Dish("pizza", true, 550, Dish.Type.OTHER)
                , new Dish("prawns", false, 300, Dish.Type.FISH)
                , new Dish("salmon", false, 450, Dish.Type.FISH));
    
            /*
             * 8.1.1 코드 가독성 개선
             * 
             * 일반적으로 코드 가독성이 좋다는 것은 '어떤 코드를 다른 사람도 쉽게 이해할 수 있음'을 의미한다.
             * 즉, 코드 가독성을 개선한다는 것은 우리가 구현한 코드를 다른 사람이 쉽게 이해하고 유지보수할 수 있게
             * 만드는 것을 의미한다.
             * 코드 가독성을 높이려면 코드의 문서화를 잘하고, 표준 코딩 규칙을 준수하는 등의 노력을 기울여야 한다.
             * 
             * 자바 8에서는 코드 가독성에 도움을 주는 다음과 같은 기능을 새롭게 제공한다.
             *         - 코드의 장황함을 줄여 쉽게 이해할 수 있는 코드를 구현할 수 있다.
             *         - 메서드 레퍼런스의 스트림 API를 이용해서 코드의 의도(무엇을 수행하려는 것인지)를 쉽게 표현할 수 있다.
             * 
             * 8.1.2 익명 클래스를 람다 표현식으로 리팩토링하기
             * 
             * 하나의 추상 메서드를 구현하는 익명 클래스는 람다 표현식으로 리팩토링할 수 있다.
             * 익명 클래스는 코드를 장황하게 만들고 쉽게 에러를 일으킨다.
             * 람다 표현식을 이용해서 간결하고, 가독성이 좋은 코드를 구현할 수 있다.
             * 
             * - 익명 클래스를 사용한 이전 코드
             * Runnable r1 = new Runnable() {
             *         public void run() {
             *             System.out.println("Hello");
             *         }
             * }
             * 
             * - 람다 표현식을 사용한 최신 코드
             * Runnable r2 = () -> System.out.println("Hello!!");
             * 
             * 하지만 모든 익명 클래스를 람다 표현식으로 변환할 수 있는것은 아니다.
             *         1. 익명 클래스에서 사용한 this와 super는 람다 표현식에서 다른 의미를 갖는다.
             *         익명 클래스에서 this는 익명 클래스 자신을 가리키지만 람다에서 this는 람다를 감싸는 클래스를 가리킨다.
             *         2. 익명 클래스는 감싸고 있는 클래스의 변수를 가릴 수 있다(섀도 변수-shadow variable).
             *         하지만 다음 코드에서 처럼 람다 표현식으로는 변수를 가릴 수 없다.
             * 
             *         int a = 10;
             *         Runnable r1 = () -> {
             *             int a = 2; // 컴파일 에러.
             *             System.out.println(a);
             *         }
             * 
             *         Runnabel r2 =  new Runnable() { 
             *             public void run() {
             *                 int a = 2; // 모든 것이 잘 작동한다.
             *                 System.out.println(a);
             *             }
             *         }
             * 익명 클래스를 람다 표현식으로 바꾸면 콘텍스트 오버로딩에 따른 모호함이 초래될 수 있다.
             * 익명 클래스는 인스턴스화할 때 명시적으로 형식이 정해지는 반명 람다의 형식은 콘텍스트에 따라 달라지기 때문이다.
             */
    
            // Task를 구현하는 익명 클래스르 전달할 수 있다.
            Task.doSomthing(new Task() {            
                @Override
                public void execute() {
                    System.out.println("익명 클래스 - Task Danger danger!!!");
                }
            });
    
            Task.doSomthing(new Runnable() {
    
                @Override
                public void run() {
                    System.out.println("익명 클래스 - Runabel Danger danber!!");
                }
            }); 
    
            /*
             * 위 익명 클래스를 람다 표현식으로 바꾸면 메서드를 호출할 때 Runnable와 Task 모두 대상 형식이 될 수 있으므로 모호함이 발생한다. 
             */        
            // 모호함 때문에 아래 주석을 해제하면 에러가 발생한다. 
            // Task.doSomthing(() -> System.out.println("Danger danger"));
    
            // 명시적 형변환을 이용해서 모호함을 제거할 수 있다.        
            Task.doSomthing((Task)() -> System.out.println("람다 표현식 - Task Danger danger"));        
            Task.doSomthing((Runnable)() -> System.out.println("람다 표현식 - Runnable Danger danger"));
    
            /*
             * 8.1.3 람다 표현식을 메서드 레퍼런스로 리팩토링하기
             * 
             * 람다 표현식 대신 메서드 레퍼런스를 이용하면 가독성을 높일 수 있다.
             * 메서드 레퍼런스의 메서드명으로 코드의 의도를 명확하게 알릴 수 있기 때문이다.
             */
            Map<Dish.CaloricLevel, List<Dish>> dishesByCaloricLevel = menu.stream()
                .collect(Collectors.groupingBy(dish -> {
                    if(dish.getCalories() <= 400) {
                        return Dish.CaloricLevel.DIET;
                    } else if(dish.getCalories() <= 700) {
                        return Dish.CaloricLevel.NORMAL;
                    } else {
                        return Dish.CaloricLevel.FAT;
                    }
                }));
    
            System.out.println("8.1.3 람다 표현식을 메서드 레퍼런스로 리팩토링하기 - 람다 표현식 : " + dishesByCaloricLevel);
    
            /*
             * 람다 표현식을 별도의 메서드로 추출한 다음에 groupingBy에 인수로 전달할 수 있다.
             */
            dishesByCaloricLevel = menu.stream()
                .collect(Collectors.groupingBy(Dish::getCaloricLevel));
    
            System.out.println("8.1.3 람다 표현식을 메서드 레퍼런스로 리팩토링하기 - 메서드 레퍼런스 : " + dishesByCaloricLevel);
    
            /*
             * sum, maximum 등 자주 사용하는 리듀싱 연산은 메서드 레퍼런스와 함께 사용할 수 있는 내장 헬퍼 메서드를 제공한다.
             * 최대값이나 합계를 계산할 때 람다 표현식과 저수준 리듀싱 연산을 조합하는것 보다 Collectors API를 사용하면 코드의 의도가
             * 더 명확해진다.
             */
    
            // 저수준 리듀싱 조합 코드.
            int totalCalories = menu.stream()
                    .map(Dish::getCalories)
                    .reduce(0, (c1, c2) -> c1 + c2);
    
            System.out.println("8.1.3 람다 표현식을 메서드 레퍼런스로 리팩토링하기 - 저수준 리듀싱 조합 : " + totalCalories);
    
            // Collectors API 코드.
            totalCalories = menu.stream()
                .collect(Collectors.summingInt(Dish::getCalories));
    
            System.out.println("8.1.3 람다 표현식을 메서드 레퍼런스로 리팩토링하기 - Collectors API : " + totalCalories);
    
            /*
             * 8.1.4 명령형 데이터 처리를 스트림으로 리팩토링하기
             * 
             * 이론적으로는 반복자를 이용한 기존의 모든 컬렉션 처리 코드를 스트림 API로 바꿔야 한다.
             * 스트림 API는 데이터 처리 파이프라인의 의도를 더 명확하게 보여주고 쇼트서킷과 게으름이라는
             * 강력한 최적화뿐 아니라 멀티코어 아키텍처를 활용할 수 있는 지름길을 제공한다.
             */
    
            /*
             * 아래 명령형 코드는 필터링과 추출이란 두 가지 패턴으로 엉킨 코드다.
             * 이런 코드를 접한 다른 개발자는 전체 구현을 자세히 살펴본 이후에야 전체 코드의 의도를 파악할 수 있을것이다.
             */
            List<String> dishNames = new ArrayList<>();
            for(Dish dish : menu) {
                if(dish.getCalories() > 300) {
                    dishNames.add(dish.getName());
                }
            }
    
            System.out.println("8.1.4 명령형 데이터 처리를 스트림으로 리팩토링하기 - 명령형 코드 : " + dishNames);
    
            /*
             * 스트림 API를 이용하면 문제를 좀 더 직접적으로 기술할 수 있을 뿐 아니라 쉽게 병렬화할 수 있다.
             */
            dishNames = menu.stream()
                .filter((dish) -> dish.getCalories() > 300)
                .map(Dish::getName)
                .collect(Collectors.toList());
    
            System.out.println("8.1.4 명령형 데이터 처리를 스트림으로 리팩토링하기 - 스트림 코드 : " + dishNames);
    
            /*
             * 8.1.5 코드 유연성 개선
             * 
             * 람다 표현식을 이용하면 "동작 파라미터화(behaviour parameterization)"를 쉽게 구현할 수 있다.
             * 즉, 다양한 람다를 전달해서 다양한 동작을 표현할 수 있다. 따라서 변화하는 요구사항에 대응할 수 있는 코드를
             * 구현할 수 있다. 
             */
        }
    
    }

    8.2 람다로 객체지향 디자인 패턴 리팩토링하기

    Design pattern

    Chain

    Abstract

    package Part3.Chapter8.Chapter8_8_2.chain.abs;
    
    public abstract class ProcessingObject<T> {
    
        protected ProcessingObject<T> successor;
    
        public void setSuccessor(ProcessingObject<T> successor) {
            this.successor = successor;
        }
    
        public T handle(T input) {
            T r = handleWork(input);
    
            if(this.successor != null) {
                return this.successor.handle(r);
            }
    
            return r;
        }
    
        abstract protected T handleWork(T input);
    }

    Implement

    HeaderTextProcessing.java

    package Part3.Chapter8.Chapter8_8_2.chain.impl;
    
    import Part3.Chapter8.Chapter8_8_2.chain.abs.ProcessingObject;
    
    public class HeaderTextProcessing extends ProcessingObject<String>{
    
        @Override
        protected String handleWork(String input) {
            return "From Raoul. Mario and Alan : " + input;
        }
    
    }

    SpellCheckerProcessing.java

    package Part3.Chapter8.Chapter8_8_2.chain.impl;
    
    import Part3.Chapter8.Chapter8_8_2.chain.abs.ProcessingObject;
    
    public class SpellCheckerProcessing extends ProcessingObject<String>{
    
        @Override
        protected String handleWork(String input) {
            return input.replaceAll("labda", "lambda");                
        }
    
    }

    Factory

    Entity

    Product.java

    package Part3.Chapter8.Chapter8_8_2.factory.product;
    
    public class Product {
    
        protected String name;
    
        public Product() { }
    
        public Product(String name) {
            this.name = name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String getName() {
            return this.name;
        }
    
        @Override
        public String toString() {
            return "{" 
                + "name : " + this.name
                + "}";
        }
    }

    Stock.java

    package Part3.Chapter8.Chapter8_8_2.factory.product;
    
    public class Stock extends Product{
        public Stock() {
            super.name = "stock";
        }
    }

    Bond.java

    package Part3.Chapter8.Chapter8_8_2.factory.product;
    
    public class Bond extends Product {
        public Bond() {
            super.name = "bond";
        }
    }

    Loan.java

    package Part3.Chapter8.Chapter8_8_2.factory.product;
    
    public class Loan extends Product {
        public Loan() {
            super.name = "loan";
        }
    }

    Implement

    LambdaProductFactory.java

    package Part3.Chapter8.Chapter8_8_2.factory;
    
    import java.util.HashMap;
    import java.util.Map;
    import java.util.function.Supplier;
    
    import Part3.Chapter8.Chapter8_8_2.factory.product.Bond;
    import Part3.Chapter8.Chapter8_8_2.factory.product.Loan;
    import Part3.Chapter8.Chapter8_8_2.factory.product.Product;
    import Part3.Chapter8.Chapter8_8_2.factory.product.Stock;
    
    public class LambdaProductFactory {
        final static Map<String, Supplier<Product>> map = new HashMap<>();
    
        static {
            map.put("loan", Loan::new);
            map.put("stock", Stock::new);
            map.put("bond", Bond::new);
        }
    
        public static Product createProduct(String name) {
            Supplier<Product> p = map.get(name);
    
            if(p != null) {
                return p.get();
            }
    
            throw new IllegalArgumentException("no such product " + name);
        }
    }

    ProductFactory.java

    package Part3.Chapter8.Chapter8_8_2.factory;
    
    import Part3.Chapter8.Chapter8_8_2.factory.product.Bond;
    import Part3.Chapter8.Chapter8_8_2.factory.product.Loan;
    import Part3.Chapter8.Chapter8_8_2.factory.product.Product;
    import Part3.Chapter8.Chapter8_8_2.factory.product.Stock;
    
    public class ProductFactory {
        public static Product createProduct(String name) {
            switch (name) {
                case "loan" : return new Loan();
                case "stock" : return new Stock();
                case "bond" : return new Bond();
                default : throw new RuntimeException("No such product " + name);
            }
        }
    
    }

    Observer

    Interface

    Observer.java

    package Part3.Chapter8.Chapter8_8_2.observer.inter;
    
    public interface Subject {
        void registerObserver(Observer o);
        void notifyObservers(String tweet);
    }

    Subject.java

    package Part3.Chapter8.Chapter8_8_2.observer.inter;
    
    public interface Observer {
        void notify(String tweet);
    }

    Implement

    Guardian.java

    package Part3.Chapter8.Chapter8_8_2.observer.impl;
    
    import Part3.Chapter8.Chapter8_8_2.observer.inter.Observer;
    
    public class Guardian implements Observer {
    
        @Override
        public void notify(String tweet) {
            if(tweet != null && "queen".equals(tweet)) {
                System.out.println("Yet another news in London... " + tweet);
            }
        }
    
    }

    LeMonde.java

    package Part3.Chapter8.Chapter8_8_2.observer.impl;
    
    import Part3.Chapter8.Chapter8_8_2.observer.inter.Observer;
    
    public class LeMonde implements Observer {
    
        @Override
        public void notify(String tweet) {
            if(tweet != null && "wine".equals(tweet)) {
                System.out.println("Today cheese, wine and news! " + tweet);
            }
        }
    
    }

    NYTimes.java

    package Part3.Chapter8.Chapter8_8_2.observer.impl;
    
    import Part3.Chapter8.Chapter8_8_2.observer.inter.Observer;
    
    public class NYTimes implements Observer {
    
        @Override
        public void notify(String tweet) {
            if(tweet != null && "money".equals(tweet)) {
                System.out.println("Breaking new in NY! " + tweet);
            }
        }
    
    }

    Feed.java

    package Part3.Chapter8.Chapter8_8_2.observer.impl;
    
    import java.util.ArrayList;
    import java.util.List;
    
    import Part3.Chapter8.Chapter8_8_2.observer.inter.Observer;
    import Part3.Chapter8.Chapter8_8_2.observer.inter.Subject;
    
    public class Feed implements Subject {
        private final List<Observer> observers = new ArrayList<>();    
    
        @Override
        public void registerObserver(Observer o) {
            this.observers.add(o);
        }
    
        @Override
        public void notifyObservers(String tweet) {
            this.observers.forEach(o -> o.notify(tweet));        
        }
    }

    Strategy

    Interface

    ValidationStrategy.java

    package Part3.Chapter8.Chapter8_8_2.strategy.inter;
    
    public interface ValidationStrategy {
        boolean execute(String s);
    }

    Implement

    IsNumeric.java

    package Part3.Chapter8.Chapter8_8_2.strategy.inter;
    
    public class IsNumeric implements ValidationStrategy {
    
        @Override
        public boolean execute(String s) {
            return s.matches("\\\\d+");
        }
    
    }

    IsAllLowerCase.java

    package Part3.Chapter8.Chapter8_8_2.strategy.inter;
    
    public class IsAllLowerCase implements ValidationStrategy {
    
        @Override
        public boolean execute(String s) {
            return s.matches("[a-z]+");        
        }
    
    }

    Validator.java

    package Part3.Chapter8.Chapter8_8_2.strategy;
    
    import Part3.Chapter8.Chapter8_8_2.strategy.inter.ValidationStrategy;
    
    public class Validator {
        private final ValidationStrategy strategy;
    
        public Validator(ValidationStrategy strategy) {
            this.strategy = strategy;
        }
    
        public boolean validate(String s) {
            return this.strategy.execute(s);
        }
    }

    TemplateMethod

    Entity

    Customer.java

    package Part3.Chapter8.Chapter8_8_2.templateMethod.customer;
    
    public class Customer {
        private final String name;
        private final int id;
    
        public Customer(String name, int id) {
            this.name = name;
            this.id = id;
        }
    
        public String getName() {
            return this.name;
        }
    
        public int getId() {
            return this.id;
        }
    
        @Override
        public String toString() {
            return "{"
                + "name : " + this.name
                + ", id : " + this.id
                + "}";
        }     
    }

    Abstract

    OnlineBanking.java

    package Part3.Chapter8.Chapter8_8_2.templateMethod.abs;
    
    import java.util.function.Consumer;
    import java.util.function.Function;
    
    import Part3.Chapter8.Chapter8_8_2.templateMethod.Database;
    import Part3.Chapter8.Chapter8_8_2.templateMethod.customer.Customer;
    
    public abstract class OnlineBanking {
        Function<Integer, Customer> getCustomerWithId = (id) -> Database.getCustomerWithId(id);  
    
        public void processCustomer(int id) {
            Customer c = this.getCustomerWithId.apply(id);
    
            this.makeCustomerHappy(c);
        }
    
        public void processCustomer(int id, Consumer<Customer> makeCustomerHappy) {
            Customer c = this.getCustomerWithId.apply(id);
    
            makeCustomerHappy.accept(c);
        }
    
        public Function<Integer, Customer>  rtnCustomerWithId() {
            return this.getCustomerWithId;
        }
    
        public abstract void makeCustomerHappy(Customer c);
    }

    Implement

    OnlineBankingLambda.java

    package Part3.Chapter8.Chapter8_8_2.templateMethod.impl;
    
    import Part3.Chapter8.Chapter8_8_2.templateMethod.abs.OnlineBanking;
    import Part3.Chapter8.Chapter8_8_2.templateMethod.customer.Customer;
    
    public class OnlineBankingLambda extends OnlineBanking {
    
        @Override
        public void makeCustomerHappy(Customer c) { }
    
    }

    OnlineDepositBanking.java

    package Part3.Chapter8.Chapter8_8_2.templateMethod.impl;
    
    import Part3.Chapter8.Chapter8_8_2.templateMethod.abs.OnlineBanking;
    import Part3.Chapter8.Chapter8_8_2.templateMethod.customer.Customer;
    
    public class OnlineDepositBanking extends OnlineBanking {
    
        @Override
        public void makeCustomerHappy(Customer c) {
            System.out.println(c.getName() + "님 입금이 완료 되었습니다.");
        }
    }

    OnlineWithdrawBanking.java

    package Part3.Chapter8.Chapter8_8_2.templateMethod.impl;
    
    import Part3.Chapter8.Chapter8_8_2.templateMethod.abs.OnlineBanking;
    import Part3.Chapter8.Chapter8_8_2.templateMethod.customer.Customer;
    
    public class OnlineWithdrawBanking extends OnlineBanking  {
    
        @Override
        public void makeCustomerHappy(Customer c) {
            System.out.println(c.getId() + " 아이디를 가지신 " + c.getName() + "님 출금이 완료 되었습니다.");
        }
    
    }

    Database.java

    package Part3.Chapter8.Chapter8_8_2.templateMethod;
    
    import java.util.Arrays;
    import java.util.List;
    import java.util.stream.Collectors;
    
    import Part3.Chapter8.Chapter8_8_2.templateMethod.customer.Customer;
    
    public class Database {
        final static List<Customer> customers = Arrays.asList(
            new Customer("kim", 1)
            , new Customer("sung", 2)
            , new Customer("wook", 3) ); 
    
        public static Customer getCustomerWithId(int id) {        
            return Database.customers.stream()
                .filter((customer) -> customer.getId() == id)
                .limit(1)
                .collect(Collectors.toList())
                .get(0);
        }
    }

    Main

    package Part3.Chapter8.Chapter8_8_2;
    
    import java.util.function.UnaryOperator;
    
    import Part3.Chapter8.Chapter8_8_2.chain.abs.ProcessingObject;
    import Part3.Chapter8.Chapter8_8_2.chain.impl.HeaderTextProcessing;
    import Part3.Chapter8.Chapter8_8_2.chain.impl.SpellCheckerProcessing;
    import Part3.Chapter8.Chapter8_8_2.factory.LambdaProductFactory;
    import Part3.Chapter8.Chapter8_8_2.factory.ProductFactory;
    import Part3.Chapter8.Chapter8_8_2.observer.impl.Feed;
    import Part3.Chapter8.Chapter8_8_2.observer.impl.Guardian;
    import Part3.Chapter8.Chapter8_8_2.observer.impl.LeMonde;
    import Part3.Chapter8.Chapter8_8_2.observer.impl.NYTimes;
    import Part3.Chapter8.Chapter8_8_2.strategy.Validator;
    import Part3.Chapter8.Chapter8_8_2.strategy.inter.IsAllLowerCase;
    import Part3.Chapter8.Chapter8_8_2.strategy.inter.IsNumeric;
    import Part3.Chapter8.Chapter8_8_2.templateMethod.impl.OnlineBankingLambda;
    import Part3.Chapter8.Chapter8_8_2.templateMethod.impl.OnlineDepositBanking;
    import Part3.Chapter8.Chapter8_8_2.templateMethod.impl.OnlineWithdrawBanking;
    
    /*
     * 8.2 람다로 객체지향 디자인 패턴 리팩토링하기
     */
    public class Main_8_2 {
    
        public static void main(String[] args) {
            /*
             * 8.2.1 전략 패턴
             * 
             * 전략 패턴은 한 유형의 알고리즘을 보유한 상태에서 런타임에 적절한 알고리즘을 선택하는 기법이다.
             */
            Validator numericValidator = new Validator(new IsNumeric());
            System.out.println("8.2.1 전략 디자인 패턴 - IsNumeric 객체 : " + numericValidator.validate("aaaa"));
    
            Validator lowerCaseValidator = new Validator(new IsAllLowerCase());
            System.out.println("8.2.1 전략 디자인 패턴 - IsAllLowerCase 객체 : " + lowerCaseValidator.validate("bbbb"));
    
            /*
             * ValidationStrategy는 함수형 인터페이스 Predicate<String>과 같은 함수 디스크립터를 갖고 있으므로
             * 다양한 전략을 새로운 클래스 구현할 필요 없이 람다 표현식으로 구현할 수 있다.
             */
            numericValidator = new Validator((s) -> s.matches("\\\\d+"));        
            System.out.println("8.2.1 전략 디자인 패턴 - IsNumeric의 람다 표현식 : " + numericValidator.validate("aaaa"));
    
            lowerCaseValidator = new Validator((s) -> s.matches("[a-z]+"));
            System.out.println("8.2.1 전략 디자인 패턴 - IsAllLowerCase의 람다 표현식 : " + lowerCaseValidator.validate("bbbb"));
    
            /*
             * 8.2.2 템플릿 메서드 패턴
             * 
             * 알고리즘의 개요를 제시한 다음에 알고리즘의 일부를 고칠 수 있는 유연함을 제공해야 할 때 템플릿 메서드 패턴을 사용한다.
             * 다시 말해 '이 알고리즘을 사용하고 싶은데 그대로는 안 되고 조금 고쳐야 하는'상황에 적합하다.
             */
            System.out.println("8.2.2 템플릿 메서드 패턴 : ");
            OnlineDepositBanking onlineDepositBanking = new OnlineDepositBanking();
            onlineDepositBanking.processCustomer(1);
    
            OnlineWithdrawBanking onlineWithdrawBanking = new OnlineWithdrawBanking();
            onlineWithdrawBanking.processCustomer(2);
    
            /*
             * 인제 OnlineBanking 클래스를 상속받지 않고 직접 람다 표현식을 전달해서 다양한 동작을 추가할 수 있다.
             */
            System.out.println("8.2.2 템플릿 메서드 패턴 - 람다 표현식 : ");
            new OnlineBankingLambda().processCustomer(3, (customer)-> {
                System.out.println(customer.getName() + "님의 찾으신 ID는 " + customer.getId() + " 입니다.");
            });        
            new OnlineBankingLambda().processCustomer(1, (customer)-> {
                System.out.println(customer.toString());
            });
            new OnlineBankingLambda().processCustomer(2, onlineDepositBanking::makeCustomerHappy);
            new OnlineBankingLambda().processCustomer(1, onlineWithdrawBanking::makeCustomerHappy);
    
            /*
             * 8.2.3 옵저버
             * 
             * 어떤 이벤트가 발생했을 때 한 객체(주체-subject라 불리는)가 다른 객체 리스트(옵저버 observer)에 자동으로 알림을
             * 보내야 하는 상황에서 옵저버 디자인 패턴을 사용한다.
             * 예를 들어 주식의 가격(주체) 변동에 반응하는 다수의 거래자(옵저버)에서도 옵저버 패턴을 사용할 수 있다.
             */
            System.out.println("8.2.3 옵저버 패턴 : ");
            Feed f = new Feed();
            f.registerObserver(new NYTimes());
            f.registerObserver(new Guardian());
            f.registerObserver(new LeMonde());
            f.notifyObservers("money");
            f.notifyObservers("queen");
            f.notifyObservers("wine");;
    
            System.out.println("8.2.3 옵저버 패턴 - 람다 표현식 : ");
            f.registerObserver((tweet) -> {
                if(tweet != null && "moenyLambda".equals(tweet)) {
                    System.out.println("Lambda Breaking new in NY! " + tweet);
                }
            });
            f.registerObserver((tweet) -> {
                if(tweet != null && "wineLambda".equals(tweet)) {
                    System.out.println("Lambda Today cheese, wine and news! " + tweet);
                }
            });
            f.registerObserver((tweet) -> {
                if(tweet != null && "queenLambda".equals(tweet)) {
                    System.out.println("Lambda Yet another news in London... " + tweet);
                }
            });
            f.notifyObservers("moenyLambda");
            f.notifyObservers("wineLambda");
            f.notifyObservers("queenLambda");
    
            /*
             * 8.1.4 의무 체인
             * 
             * 작업처리 객체의 체인(동작 체인 등)을 만들 때는 의무 체인 패턴을 사용한다.
             * 한 객체가 어떤 작업을 처리한 다음에 다른 객체로 결과를 전달하고, 다른 객체도 해야 할 작업을
             * 처리한 다음에 또 다른 객체로 전달하는 식이다.
             */
            ProcessingObject<String> p1 = new HeaderTextProcessing();
            ProcessingObject<String> p2 = new SpellCheckerProcessing();
    
            p1.setSuccessor(p2);
            System.out.println("8.2.4 의무 체인 : " + p1.handle("Aren't labdas really sexy?!!"));
    
            UnaryOperator<String> headerProcessing = (text) -> "From raoul, Mario and Alan : " + text;
            UnaryOperator<String> spellCheckerProcessing = (text) -> text.replaceAll("labda", "lambda");
    
            System.out.println("8.2.4 의무 체인 - 람다 표현식 : " + headerProcessing
                .andThen(spellCheckerProcessing)
                .apply("Aren't labdas really sexy?!!"));
    
            /*
             * 8.2.5 팩토리
             * 
             * 인스턴화 로직을 클라이언트에 노출하지 않고 객체를 만들 때 팩토리 디자인 패턴을 사용한다.
             * createProduct 메서드는 생성자와 설정을 외부로 노출하지 않음으로써 클라이언트가 단순하게
             * 상품을 생산할 수 있다.
             */
            System.out.println("8.1.5 팩토리 : " + ProductFactory.createProduct("loan"));
            System.out.println("8.1.5 팩토리 : " + ProductFactory.createProduct("stock"));
            System.out.println("8.1.5 팩토리 : " + ProductFactory.createProduct("bond"));
    
            /*
             * 여러 인수를 전달하는 상황에서는 단순한 Supplier로는 사용하기 힘들다.
             * 만약 생성자 인수가 3개가 필요하다고 한다면 세 인수를 지원하는 새로운 함수형 인터페이스를 만들어야 한다.
             *  
             *         public interface TriFunction<T, U, V, R> {
             *             R apply(T t, U u, V v);
             *         }
             * 
             *         Map<String, TriFunction<Integer, Integer, String, Product>> map = new HashMap<>();  
             */
            System.out.println("8.1.5 팩토리 - 람다 표현식 : " + LambdaProductFactory.createProduct("loan"));
            System.out.println("8.1.5 팩토리 - 람다 표현식 : " + LambdaProductFactory.createProduct("stock"));
            System.out.println("8.1.5 팩토리 - 람다 표현식 : " + LambdaProductFactory.createProduct("bond"));
    
        }
    
    }

    요약

    • 람다 표현식으로 가독성이 좋고 더 유연한 코드를 만들 수 있다.
    • 익명 클래스는 람다 표현식으로 바꾸는 것이 좋다. 하지만 이때 this, 변수 섀도 등 미묘하게 의미상 다른 내용이 있음을 주의하자. 메서드 레퍼런스로 람다 표현식보다 더 가독성이 좋은 코드를 구현할 수 있다.
    • 반복적으로 컬렉션을 처리하는 루틴은 스트림 API로 대체할 수 있을지 고려하는것이 좋다.
    • 람다 표현식으로 전략, 템플릿 메서드, 옵저버, 의무 체인, 팩토리 등의 객체지향 디자인 패턴에서 발생하는 불필요한 코드를 제거할 수 있다.
    • 람다 표현식도 단위 테스트를 수행할 수 있다. 하지만 람다 표현식 자체를 테스트하는 것보다는 람다 표현식이 사용되는 메서드의 동작을 테스트하는 것이 바람직하다.
    • 복잡한 람다 표현식은 일반 메서드로 재구현할 수 있다.
    • 람다 표현식을 사용하면 스택 트레이스를 이해하기 어려워진다.
    • 스트림 파이프라인에서 요소를 처리할 때 peek 메서드로 중간값을 확인할 수 있다.
    반응형

    댓글

Designed by Tistory.