이 장의 주요 내용
- 변화하는 요구사항에 대응
- 동작 파라미터화
- 익명 클래스
- 람다 표현식 미리보기
우리가 어떤 상황에서 일을 하든 소비자 요구사항은 항상 바뀐다.
변화하는 요구사항은 소프트웨어 엔지니어링에서 피할 수 없는 문제다.
→ 이를 동작 파라미터화를 아용하면? 자주 바뀌는 요구사항에 효과적으로 대응할 수 있다!
→ 동작 파라미터화란 아직은 어떻게 실행할 것인지 결정하지 않은 코드 블럭을 의미한다.
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;
}
filterRedApples()
를 만들고, 조건문만 바꾼다.public static List<Apple> filterApplesByColor(List<Apple> inventory, **Color color**) {
List<Apple> result = new ArrayList<>();
for (Apple apple : inventory) {
if (apple.getColor() == color) {
result.add(apple);
}
}
return result;
}
List<Apple> greenApples = filterApplesByColor(inventory, Color.GREEN);
List<Apple> redApples = filterApplesByColor(inventory, Color.RED);
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;
}
filter()
라는 메서드로 합치는 방법도 있다.public static List<Apple> filterApples(
List<Apple> inventory, Color color, int weight, boolean falg
) {
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 = filterApples(inventory, GREEN, 0, true);
public interface ApplePredicate {
boolean test(Apple apple);
}
ApplePredicate
를 정의할 수 있다.static class AppleWeightPredicate implements ApplePredicate {
@Override
public boolean test(Apple apple) {
return apple.getWeight() > 150;
}
}
static class AppleColorPredicate implements ApplePredicate {
@Override
public boolean test(Apple apple) {
return apple.getColor() == Color.GREEN;
}
}
filter
메소드가 다르게 동작할 것이라고 예상할 수 있다.filterApples()
에서 ApplePredicate
객체를 받아 검사하도록 수정해야 한다.public static List<Apple> filter(List<Apple> inventory, ApplePredicate p) {
List<Apple> result = new ArrayList<>();
for (Apple apple : inventory) {
if (p.test(apple)) {
result.add(apple);
}
}
return result;
}
코드/동작 전달하기
ApplePredicate
객체에 의해 filterApples()
의 동작이 결정된다.test()
메서드이다.test()
메서드를 ApplePredicate
로 감싸서 전달했다.→ 그래도 이를 구현한 객체를 이용해서 불리언 표현식을 전달하므로 ‘코드를 전달’ 한 것이나 다름없다.
한 개의 파라미터, 다양한 동작
public interface AppleFormatter {
String accept(Apple a);
}
public class AppleFancyFormatter implements AppleFormatter {
public String accept(Apple apple) {
String characteristic = apple.getWeight() > 150 ? "heavy" : "light";
return "A " + characteristic + " " + apple.getColor() + " apple";
// ex) A light green apple
// ex) A heavy red apple
}
}
public class AppleSimpleFormatter implements AppleFormatter {
public String accept(Apple apple) {
return "An apple of " + apple.getWeight() + "g";
// ex) An apple of 80g
// ex) An apple of 155g
}
}
→ 지금까지 동작을 추상화해서 변화하는 요구사항에 대응할 수 있는 코드를 구현하는 방법을 살펴봤다.
→ 하지만, 여러 클래스를 구현해서 인스턴스화하는 과정이 거추장스러울 수 있다.
→ 이를 개선해보자!
filterApples()
메소드로 새로운 동작을 전달하려면?ApplePredicate
인터페이스를 구현하는 여러 클래스를 정의해야 하고, 이를 인스턴스화 해야 한다.ApplePredicate
를 구현하는 객체를 만들기List<Apple> redApples = filterApples(inventory, **new ApplePredicate()** {
public boolean test(Apple apple) {
return RED.equals(apple.getColor());
}
});
button.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent event) {
System.out.println("Whoooo a click!!");
}
});
List<Apple> redApples = filterApples(inventory, new ApplePredicate() {
public boolean test(Apple apple) { //반복되어 지저분한 코드
return RED.equals(apple.getColor());
}
}
button.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent event) {
System.out.println("Whoooo a click!!");
}
});
/* 다음 코드를 실행한 결과는 4, 5, 6, 10, 42 중 어느 것일까? */
public class MeaningOfThis {
public final int value = 4;
public void doIt() {
int value = 6;
Runnable r = new Runnable() {
public final int value = 5;
@Override
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(); // ???
}
}
결과
- 익명 클래스로 인터페이스를 구현하는 여러 클래스를 선언하는 과정을 조금 줄일 수 있지만 여전히 불만족스럽다.
- 또한 코드 조각을 전달하는 과정에서 결국은 객체를 만들고 명시적으로 새로운 동작을 정의하는 메소드를 구현해야 하는 점은 변하지 않았다.
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()));
// 정수, 문자열의 리스트에도 사용할 수 있다.
List<Integer> evenNumbers = filter(numbers, (Integer i) -> i % 2 == 0);
sort()
메소드가 포함되어 있다.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) -> a2.getWeight().compareTo(a2.getWeight()));
void run()
메소드를 포함하는 익명 클래스가 Runnable 인터페이스를 구현하도록 하는 것이 일반적이다.Thread t = new Thread(new Runnable() {
public void run() {
System.out.println("Hello world");
}
});
Thread t = new Thread(() -> System.out.println("Hello world"));
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.currrentThread().getName();
}
});
Future<String> threadName = executorService.submit(() -> Thread.currentThread().getName));
좋은 정보네요.