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

    동작 파라미터화

    아직 어떻게 실행할 것인지 결정하지 않은 코드 블록

    코드 블록은 나중에 프로그램에서 호출함 (ex. 실행될 메서드의 인수로 코드 블록을 전달)

     

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

    예제: 기존의 농장 재고목록 애플리케이션의 리스트에서 녹색 사과만 필터링하는 기능을 추가

     

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

    enum Color {RED, GREEN}
    
    public static List<Apple> filterGreenApples(List<Apple> inventory){
    	List<Apple> result = new ArrayList<>(); // 사과 누적 리스트
        for (Apple apple:inventory){
        	if (GREEN.equals(apple.getColor()){ // 녹색 사과만 선택
            	result.add(apple);
            }
        }
        return result;
    }

     

     

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

    녹색사과 말고 다른 색 사과도 필터링하고 싶은 경우

    filterGreenApples함수를 복사해서 새로운 함수를 만들수는 있으나, 필터링하고 싶은 색이 많아졌을 때 적절하게 대응할 수 없음. 그래서 코드를 추상화하려고 함

    메서드에 색 파라미터를 추가!

    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;
    }
    
    List<Apple> redApples = filterAppleByColor(inventory, RED);

     

    무게에도 필터를 적용하고 싶다면?

    그러면 filterApplesByColor를 응용해서 filterApplesByWeight를 만들면 됨

    근데 이것도 필터링하고 싶은 조건이 많아졌을 때 적절하게 대응할 수 없음

    똑같이 코드를 복사하는 것이기 때문

     

     

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

    public static List<Apple> filterApples(List<Apple> inventory, Color color, int weight, boolean flag){
    	List<Apple> result = new ArrayList<>();
        for (Apple apple: inventory){
        	if((flag && apple.getColor().equals(color)) || //flag: T --> 색, F-->무게
               (!flag && apple.getWeight() > weight)){
               	result.add(apple);
               }
        }
        return result;
    }
    
    List<Apple> heavyApples = filterApples(inventory, null, 150, false);

    flag가 뭘 의미하는지 직관적으로 알 수 없고, 앞으로 요구사항이 바뀌었을 때 유연하게 대응할 수 없음

     

     


    2.2 동작 파라미터화

    파라미터에 조건을 추가하는 것은 변화하는 요구사항에 유연하게 대처하는데 한계가 존재함

    위의 모든 필터링에서 사과의 어떤 속성에 기초해서 불리언 값을 반환했음

    이 선택 조건 자체를 정의하는 인터페이스를 만들어보자

    public class ApplePredicate{
    	public boolean test(Apple apple);
    }
    
    // ApplePredicate를 구현하는 class 정의
    // 1. 무게가 150보다 많이 나가는 사과만 반환
    public class AppleHeavyWeightPredicate implements ApplePredicate{
    	public boolean test(Apple apple){
        	return apple.getWieght() > 150;
        }
    }
    
    // 2. 녹색 사과만 반환
    public class AppleGreenColorPredicate implements ApplePredicate{
    	public boolean test(Apple apple){
        	return GREEN.equals(apple.getColor());
        }
    }

     

    조건에 따라 filter 메서드가 다르게 동작할 것이라고 예상할 수 있다!

    이를 전략 디자인 패턴이라고 부르는데, 각 알고리즘을 캡슐화하는 알고리즘 패밀리를 정의해둔 다음 런타임에 알고리즘을 선택하는 기법이다.

     

    ApplePredicate가 다양한 동작을 수행하하려면, filterApples에서 ApplePredicate 객체를 받아 사과의 조건을 검사하도록 메서드를 고쳐야 한다. 메서드(filterApples) 동작(ApplePredicate 객체)을 받아서 그 동작을 수행할 수 있도록 하는 것을 동작 파라미터화라고 한다.

     

     

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

    public static List<Apple> filterApples(List<Apple> inventory, ApplePredicate p){
    	List<Apple> result = new ArrayList<>();
        for(Apple apple: inventory){
        	if(p.test(apple)){ // 사과의 검사 조건을 캡슐화
            	result.add(apple);
            }
        }
        return result;
    }

    이제 원하는 조건이 많이 생겨도 ApplePredicate를 구현하는 class를 이용해 대응이 가능하다

     


    2.3 익명 클래스

    익명클래스는 말 그대로 이름이 없는 클래스이다.

    익명클래스를 이용하면 클래스의 선언과 인스턴스화를 동시에 수행할 수 있다!

     

    다섯 번째 시도) 익명 클래스 사용

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

    익명 클래스는 딱 봤을 때 이해하기 어려운 경우가 많다.

    익명 클래스로 인턴페이스를 구현하는 여러 클래스들을 선언하는 코드의 양을 조금 줄일 수 있지만,

    결국은 개체를만들고 명시적으로 새로운 동작을 정의하는 메소드를 구현해야 한다는 점에서 크게 변하지 않는다.

    이 문제의 대안으로 람다표현식이 있다.

     

     

    여섯 번째 시도) 람다 표현식 사용

    List<Apple> result = filterApples(inventory, (Apple apple) -> RED.equals(apple.getColor()));

    깔끔해졌다!

     

     

    [마지막] 일곱 번째 시도) 리스트 형식으로 추상화

    public interface Predicate<T>{
    	boolean test(T t);
    }
    
    public static<T> List<T> filter(List<T> list, Predicate<T> p){
    	List<T> result = new ArrayList<>();
        for(T e: list){
        	if(p.test(e)){
            	result.add(e);
            }
        }
        return result;
    }
    
    List<Apple> redApples = filter(inventory, (Apple apple)->RED.equals(apple.getColor()));

     


    2.4 실전 예제

    • Comparator
    • Runnable
    • Callable
    • GUI 이벤트 처리하기

    1) Comparator로 정렬하기

    요구사항에 쉽게 대응할 수 있는 다양한 정렬 동작 수행

    // java.util.Comparator
    public interface Comparator<T> {
    	int compare(T o1, T o2);
    }
    
    // 익명클래스 이용
    inventory.sort(new Comparator<Apple>(){
    	public int compare(Apple a1, Apple a2){
        	return a1.getWeight().compareTo(a2.getWeight());
        }
    });
    
    // 람다표현식 이용
    inventory.sort((Apple a1, Apple a2)->a1.getWeight().compareTo(a2.getWeight())));

     

    2) Runnable로 코드 블록 실행하기

    자바 스레드를 이용하면 병렬로 코드를 실행할 수 있음

    Runnable 인터페이스를 구현해서 어떤 코드를 실행할 것인지 스레드에게 알려줄 수 있음

    // java.lang.Runnable
    public interface Runnable{
    	void run();
    }
    
    // 익명 클래스
    Thread t = new Thread(new Runnable(){
    	public void run(){
        	System.out.println("Hello World!");
        }
    });
    
    // 람다 표현식
    Thread t = new Thread(()->System.out.println("Hello World!"));

     

    3) Callable을 결과로 반환하기

    ExecutorService 인터페이스는 task 제출과 실행과정의 연관성을 끊어줌.

    이 인터페이스를 이용하면 task를 스레드 풀로 보내고 결과를 Future로 저장할 수 있음

    결과를 반환하는 과정에서 Callable 인터페이스를 이용함

    Runnable의 업그레이드 방식이라고 할 수 있음

    // java.util.concurrent.Callable
    public interface Callable<V>{
    	V call();
    }
    
    // 익명 클래스
    ExecutorService executorService = Executors.newCachedThreadPool();
    Future<String> threadName = executorService.submit(new Callable<String>(){
    	@Override
        public String call() throws Exception{
        	return Thread.currentThread().getName();
        }
    });
    
    // 람다표현식
    Future<String> threadName = executorService.submit(()->Thread.currentThread().getName());

     

    4) GUI 이벤트 처리기

    자바FX에서는 setOnAction 메서드에 EventHandler를 전달함으로써 어떻게 반응하지 설정할 수 있음

    Button button = new Button("Send");
    // 익명클래스
    button.setOnAction(new EventHandler<ActionEvent>(){
    	public void handle(ActionEvent event){
        	label.setText("Sent!");
        }
    });
    
    // 람다
    button.setOnAction((ActionEvent event)->label.setText("Sent!"));

     

    댓글