[모던 자바 chap 2] 동작 파라미터화 코드 전달

박상준·2024년 10월 19일
0

개요

  • SW 상에서 변화하는 요구사항 은 피할 수 없는 문제이다.
  • 변화하는 요구사항에 맞춰 대응 → 효율적인 코드 설계 필요

동작 파라미터화 (Behavior Parameterization)

  • 코드 블록의 동작을 미리 결정하지 않고,
    • 필요할 때 그 동작을 나중에 정의하는 기법

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

녹색 사과를 필터링

public class 변화하는요구사항대응하기 {
    public static List<Apple> filterGreenApples(List<Apple> inventory) {
        List<Apple> result = new ArrayList<>();
        
        for (Apple apple : inventory) {
            if (apple.getColor() == Color.GREEN) {
                result.add(apple);
            }
        }
        
        return result;
    }
    
    class Apple {
        private Color color;
        
        public Color getColor() {
            return color;
        }
    }
    
    enum Color {
        RED, GREEN
    }
}
  • 녹색사과만 재고에서 골라내는 로직이다.
  • 녹색사과만을 필터링이 가능하여 다른 요구사항에 대하여 대응이 어려움

색상을 파라미터화한다.

    public static List<Apple> filterRedApples(List<Apple> inventory, Color color) {
        List<Apple> result = new ArrayList<>();
        
        for (Apple apple : inventory) {
            if (apple.getColor() == color) {
                result.add(apple);
            }
        }
        return result;
    }

무게 기준의 추가

  • 새로운 요구사항으로 무게에 따른 필터링이 추가되는 경우…
  • 사과의 무게가 150g 이상이면 무거운 사과로 간주.
    • 해당 기준에 맞춰 필터링하는 메서드 추가가 가능하다.
    public static List<Apple> filterApplesByWeight(List<Apple> inventory, int weight) {
        List<Apple> result = new ArrayList<>();
        
        for (Apple apple : inventory) {
            if (apple.getWeight() > weight) {
                result.add(apple);
            }
        }
        
        return result;
    }

조건을 일반화한 필터링

  • 색상, 무게 등 다양한 조건을 처리할 수 있는 유연한 구조 만들기
  • 동작 파라미터화
    public static List<Apple> filterApplesByWeight(List<Apple> inventory, Predicate<Apple> predicate) {
        List<Apple> result = new ArrayList<>();
        
        for (Apple apple : inventory) {
            if (predicate.test(apple)) {
                result.add(apple);
            }
        }
        
        return result;
    }
    public static void main(String[] args) {
        List<Apple> inventories = List.of(
                new Apple(Color.RED, 100),
                new Apple(Color.GREEN, 200),
                new Apple(Color.RED, 300),
                new Apple(Color.GREEN, 400)
        );
        
        List<Apple> filteredApples = filterApplesByWeight(inventories, 200);
        
        System.out.println(filteredApples);
    }
    
    [Apple{color=RED, weight=300}, Apple{color=GREEN, weight=400}]
  • 원하는 조건을 ApplePredicate 인터페이스를 사용해 사과 필터 조건을 추상화가 가능함

퀴즈 2-1: 유연한 prettyPrintApple 메서드 구현하기

문제 설명

주어진 prettyPrintApple 메서드는 사과 리스트를 인수로 받아 각 사과에 대한 정보를 출력하는 기능을 가지고 있다. 이 메서드는 다양한 방식으로 사과의 정보를 출력할 수 있어야 하므로, 그 출력 방식을 파라미터화하는 것이 목적이다. 즉, 사과의 무게를 출력할 수도 있고, 사과가 무거운지 가벼운지 여부를 출력할 수도 있다.

문제 해결을 위한 힌트

이전 예제에서 사용했던 ApplePredicate 패턴과 유사하게, 출력을 제어하는 동작 파라미터화가 필요하다. 이를 위해 Function<Apple, String>과 같은 인터페이스나 람다식을 활용하여 출력을 커스터마이징할 수 있다.

public static void main(String[] args) {
    List<Apple> inventories = List.of(
            new Apple(Color.RED, 100),
            new Apple(Color.GREEN, 200),
            new Apple(Color.RED, 300),
            new Apple(Color.GREEN, 400)
    );
    
    prettyPrintApple(inventories, new ApplePrettyPrintFormatter());
}

public static void prettyPrintApple(List<Apple> inventories, AppleFormatter predicate) {
    for (Apple apple : inventories) {
        String message = predicate.accept(apple);
        
        System.out.println(message);
    }
}

public interface AppleFormatter {
    String accept(Apple apple);
}

public static class ApplePrettyPrintFormatter implements AppleFormatter {
    @Override
    public String accept(Apple apple) {
        String characteristic = apple.getWeight() > 150 ? "heavy" : "light";
        
        return MessageFormat.format("{0} apple with {1} weight", apple.getColor(), characteristic);
    }
}

5 방법 : 익명 클래스 사용

  //익명 클래스 사용하기
        List<Apple> greenApples = filterApples(inventories, new ApplePredicate() {
            @Override
            public boolean test(Apple apple) {
                return Color.RED == apple.getColor();
            }
        });
  • 익명클래스 사용으로
    • 즉석으로 그 자리에서 구현이 가능해짐
  • 하지만 해당 방법도 그리 간략해보이지는 않는다.
  • 일단 재사용이 안된다는게 조금 짜증남.

익명 클래스의 단점에 대한 퀴즈

public class MeaningOfThis {
    public final int value = 4;

    public void doIt() {
        int value = 6;
        Runnable r = new Runnable() {
            public final int value = 5; //
            public void run() {
                int value = 10;
                System.out.println(this.value);
            }
        };
        r.run();
    }

    public static void main(String... args) {
        MeaningOfThis m = new MeaningOfThis();
        m.doIt();
    }
}
  • 출력 결과는 무엇일까?
    • 정답은 5임.
  • 왜 5가 출력되는지
    • 일단 클래스내에서 this 를 사용하는 경우에는
    • 자기 자신 을 보통 가리킨다.
    • 만약 익명 클래스에서 this 를 사용한다면 어떻게 될까?
      • Runnable 인터페이스를 구현하는 익명 클래스가 선언되어 있다.
      • 익명 클래스내에서 this 는 익명 클래스를 가리키는 데
      • 익명 클래스내에는 value 라는 필드가 있고 5 로 설정
    • run 메서드 실행시 this.value 는 익명클래스의 필드을 가리키기에 5 가 출력된다.

람다식 사용하는 경우

  • 람다식의 this 는 바깥 클래스를 참조한다
  • 익명 클래스 대신 람다식을 사용한다면 MeaningOfThis 클래스의 value 가 참조되어 4 가 출력된다.

6번째 방법 : 람다식 사용

//람다식 사용하기
filterApples(inventories, apple -> Color.RED == apple.getColor())
        .forEach(System.out::println);

7번째 방법 : 리스트 형식으로 추상화

public interface Predicate<T> {
    boolean test(T t);
}

public static <T> List<T> filter(List<T> list, Predicate<T> predicate) { // 형식 파라미터
    List<T> result = new ArrayList<>();
    
    for (T t : list) {
        if (predicate.test(t)) {
            result.add(t);
        }
    }
    
    return result;
}

//람다식 사용하기
filter(inventories, apple -> Color.RED == apple.getColor()).forEach(System.out::println);

List<Integer> evenNumbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

filter(evenNumbers, number -> number % 2 == 0).forEach(System.out::println);
  • 사과 등의 타입말구도 정수, 문자열 등에서도 필터 메서드를 사용할 수 있게 된다.

동작 파라미터화?

  • 동작을 메서드의 파라미터로 넘겨줄 수 있도록 하는 패턴임.
  • 변화하는 요구사항에 쉽게 적응이 가능하다

Comparator 로 정렬

  • 자바 8 에서 List 에 sort 메서드가 추가됨.
  • Comparator 인터페이스를 사용해 정렬 동작 파라미터화 가능
// 익명 클래스 사용해 무게 기준 정렬
inventories.sort(new Comparator<Apple>() {
    @Override
    public int compare(Apple o1, Apple o2) {
        return o1.getWeight() - o2.getWeight();
    }
});
  • 익명클래스로 무게 기준 정렬 즉석으로 구현이 가능함.

람다로 간단하게 변경

// 람다 사용해 무게 기준 정렬
inventories.sort((o1, o2) -> o1.getWeight() - o2.getWeight());

Runnable 로 코드 블록 실행

스레드에서 코드 블록 실행

  • 자바 8 이전
    Thread t = new Thread(new Runnable() {
        public void run() {
            System.out.println("Hello world");
        }
    });
    t.start();  // 스레드를 시작
    
    • 익명 클래스로 Runnable 구현 후 코드 블록 작성

자바 8 람다표현식으로 간결하게 ㄱㄱ

Thread t = new Thread(() -> System.out.println("Hello world"));

t.start();  // 스레드를 시작
  • 익명 클래스 없이도 짧은 코드로 구현이 가능해진다.

Callable 을 사용하여 결과를 반환

  • 자바에서 태스크가 값을 반환할 수 있도록 만들어진 인터페이스
  • 기본적으로 Runnable 은 값 반환 X , Callable 은 작업이 끝난 후 결과 반환 가능
// java.util.concurrent.Callable
public interface Callable<V> {
    V call() throws Exception;
}
  • ExecutorService || Callable
    • 자바 5 부터 ExecutorService 등장
      - ExecutorService
      - 스레드 풀 관리 추상화 개념
      - Runnable 을 통해 스레드 관리하는 것에서 한 단계 발전
      - 스레드 풀에서 태스트 관리 + 실행이 간ㄷ나해짐

      ExecutorService executorService = Executors.newCachedThreadPool();
        Future<String> threadName = executorService.submit(new Callable<String>() {
            @Override
            public String call() throws Exception {
                return Thread.currentThread().getName();
            }
        });
    • executorService.submit 에서 Callable 객체 실행

      • Future 객체가 반환된다.
      • Future 는 비동기 작업의 결과를 담고 있고, 실제 작업이 완료된 이후에 결과를 가져온다.

Callable + 람다

ExecutorService executorService = Executors.newCachedThreadPool();
Future<String> threadName = executorService.submit(() -> Thread.currentThread().getName());
profile
이전 블로그 : https://oth3410.tistory.com/
post-custom-banner

0개의 댓글