본문 바로가기

IT Book Summary/ModernJavaInAction

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

사용자의 요구사항은 항상 변한다.

따라서 새로 추가하는 기능은 쉽게 구현할 수 있고 유지보수가 쉬워야한다.

 

동작 파라미터화 behavior parameterization  

: 아직은 어떻게 실행할 것인지 결정하지 않은 코드 블록. 코드블록의 실행은 나중으로 미뤄진다.

-> 나중에 실행될 메서드의 인수로 코드블록을 전달할 수 있다.

->코드블록에 따라 메서드의 동작이 파라미터화 된다.

 

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

 


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

 

enum Color { RED, GREEN }

public static List<Apple> filterGreenApple(List<Apple> inventory) {
    List<Apple> result = new ArrayList<>();
    for (Apple apple: inventory) {
        if(Green.equals(apple.getColor())) {
            result.add(apple);
        }
    }
    return result;
}

 

거의 비슷한 코드가 반복 존재한다면 그 코드를 추상화한다

 


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

 

메서드에 색을 파라미터화 해서 다양한 색을 필터링 할 수 있다.

 

public static List<Apple> filterAppleByColor(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> greenApples = filterAppleByColor(inventory, GREEN);
List<Apple> redApples = filterApplesByColor(inventory, RED);

 

색 이외의 무게가 150그램 이상인 사과도 구분하게 하려면 무게정보를 파라미터화해서 구현도 가능하다.

하지만 색 필터링 코드와 중복된 코드를 구현해야만하고 DRY dont repeat yourself 원칙을 어기는 것이다.

-> 탐색과정을 고치려면 메서드 전체 구현을 고쳐야 하므로 엔지니어링적 비용이 증가한다.

 


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

 

플래그 속성으로 기능 구분 형편없는 코드

 

public static List<Apple> filterAppleByColor(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 && apple.getWeight() > weight)) {
            result.add(apple);
        }
    }
    return result;
}

List<Apple> greenApples = filterAppleByColor(inventory, GREEN, 0, true);
List<Apple> heavyApples = filterApplesByColor(inventory, null, 150, false);

 

요구사항이 바뀌었을때 유연하게 대응할수도 없는 코드다.

 


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

 

사과 선택조건을 캡슐화 함.

전략 디자인 패턴 strategy design pattern ( http://en.wikipedia.org/wiki/Strategy_pattern 참고 )

-> 런타임에 알고리즘을 선택하는 패턴

 

public interface ApplePredicate {
    boolean test (Apple apple);
}

public class AppleHeavyWeightPredicate implements ApplePredicate {
    public boolean test(Apple apple) {
        return apple.getWeight() > 150;
    }
}

public class AppleGreenColorPredicate implements ApplePredicate {
    public boolean test(Apple apple) {
        return GREEN.equals(apple.getColor());
    }
}

public static List<Apple> filterAppleByColor(List<Apple> inventory, 
                ApplePredicate p) {
    List<Apple> result = new ArrayList<>();
    for (Apple apple: inventory) {
        //사과 검사조건을 캡슐화
        if(p.test(apple)) {
            result.add(apple);
        }
    }
    return result;
}
//요구사항이 추가되면 새로운 클래스 추가
public class AppleRedAndHeavyPredicate implements ApplePredicate {
    public boolean test(Apple apple) {
        return RED.equals(apple.getColor()) && apple.getWeight()>150 ;
    }
}
// 전달한 ApplePredicate 객체에 의해 filterApples 메서드 동작이 결정
List<Apple> reaAndHeavyApples = filterApples(inventory, new AppleRedAndHeavyPredicate());

 

메서드의 동작을 파라미터화한 것.

유연한 API를 만들때 동작 파라미터화가 중요한 역할을 한다.

 


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

 

인터페이스를 구현하는 여러 클래스를 정의한 다음 인스턴드화 해야하는 작업또한 번거롭다.

클래스 선언과 인스턴스화를 동시에 수행할 수 있는 익명클래스를 사용하면 코드를 줄일 수 있다.

: 익명클래스, 지역 클래스 local class, 블록내부에 선언된 클래스

 

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

 

코드의 장황함 verbosity 은 나쁜특성이다.

 

결국 객체를 만들고 새로운 동작을 정의하는 메서드를 구현해야 한다는 점은 변하지 않는다.

-> 람다 표현식을 사용해 정리할 수 있다.

 


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

 

 

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> resApples = filter(inventory, (Apple apple)->RED.equals(apple.getColor()));
List<Integer> evenNumbers = filter(number, (Integer i)->i%2 == 0);

 

유연하고 간결하다.

 

 


변화하는 요구사항에 쉽게 대응할 수 있는 다양한 정렬동작을 수행할 수 있는 코드가 필요하다.

무게를 기준으로 목록에서 정렬하고 싶다면?

 

Comparator 로 정렬하기 

자바 8 List 에 sort 메서드가 있다. java.util.Comparator 객체를 이용해 sort 동작을 파라미터화 할수 있다.

 

//java.util.Comparator
public interface Comparator<T> {
    int compare(T o1, T o2);
}

//sort의 동작을 다양화
//익명클래스 이용할 경우
inventory.sort(new Comparator<Apple>() {
    public int compare(Apple a1, Apple a2) {
        return a1.getWeight().compareTo(a2.Weight());
    }
});

//람다를 이용할 경우
inventory.sort (
    (Apple a1,Apple a2)->a1.getWeight().compareTo(a2.getWeight()));

 

 


Runnable 로 코드 블록 실행하기

 

자바 스레드를 이용하면 병렬로 코드블록을 실행할 수 있다.

보통 익명클래스가 Runnable 인터페이스를 구현하도록 하는 것이 일반적이었으나 

람다 표현식으로 스레드 코드를 구현할 수 있다.

 

Thread t = new Thread(new Runnable() {
    public void run() {
        System.out.println("Hello world");
    }
});

//람다 표현식
Thread t = new Thread(() -> System.out.println("Hello world"));

 


Callable 테스크 처리하기

 

ExecutorService 를 이용하면 태스크를 스레드 풀로 보내고 결과를 Future로 저장할 수 있다.

Callable 인터페이스를 이용해 결과를 반환하는 태스크를 만든다.

 

public interface Callable<V> {
    V call();
}

ExecutorService excutorService = Executor.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());

 


GUI 이벤트 처리하기

 

자바FX 에서는 setOnAction 메서드에 EventHandler를 전달해 이벤트에 어떻게 반응할지 설정할 수 있다.

 

Button button = new Button("Send");
button.setOnAction(new EventHandler<ActionEvent>() {
    public void handle(ActionEvent event){
        lable.setText("Sent!!");
    }
});

// 람다 표현식으로 구현
button.setOnAction((ActionEvent event)->label.setText("Sent!!"));