동작 파라미터화
아직 어떻게 실행할 것인지 결정하지 않은 코드 블록
코드 블록은 나중에 프로그램에서 호출함 (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!"));
'백엔드 > 모던자바인액션' 카테고리의 다른 글
ch15. CompletableFuture와 리액티브 프로그래밍 컨셉의 기초 (0) | 2023.03.23 |
---|---|
[ch4] 스트림 소개 (0) | 2022.09.19 |
[ch3] 람다 표현식 (2) (0) | 2022.09.03 |
[ch3] 람다 표현식 (1) (0) | 2022.09.02 |
ch1. 자바 8, 9, 10, 11 무슨 일이 일어나고 있는가? (0) | 2022.07.23 |
댓글