이 장의 내용
1. 변화하는 요구사항에 대응
2. 동작 파라미터화
3. 익명 클래스
4. 람다 표현식 미리보기
5. 실전 예제 : Comparator, Runnable, GUI
소비자의 요구사항은 항상 바뀐다.
농부는 이렇게 말할 것이다. '녹색 사과를 모두 찾고 싶어요' 그런데 하룻밤을 자고 일어낫더니 농부가 다시 이렇게 말하는 것이다. '150그램 이상인 사과를 모두 찾고 싶어요' 또 하룻밤을 자고 일어났더니 '150그램 이상이면서 녹색인 사과를 모두 찾을 수 잇다면 좋겠네요'
이렇게 시시각각 변화하는 사용자 요구 사항에 엔지니어링적인 비용이 가장 최소화가 되며 장기적인 괌점에서 유지보수가 쉽도록 하기 위해 동작 파라미터화(behavior parameterization)를 이용하여 대응하여야 한다.
변화하는 요구 사항에 대응하기
2.1.1 첫 번째 시도 : 녹색 사과 필터링
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;
}
- 위의 코드는 녹색 사과를 필터링하는 코드 이다.
- 현재 요구사항은 녹색 사과이지만, 빨간 사과를 필터링 해야하는 요구사항이 생길 경우 위에 코드와 유사한 코드를 다시 작성하게 되는 문제가 있다.
2.1.2 두 번째 시도 : 색을 파라미터화
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;
}
- 색을 파라미터화 하여 필터링을 구현하였다.
- 여기서 색이 아닌 무게를 이용해야 하는 요구사항이 생긴다면 어떻게 대응하여야 할까?
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;
}
- 색을 파라미터화 한 것처럼 무게를 파라미터화 하여 위와 같이 해결할 수 있다.
- 위 예시에서 색과, 무게 조건을 지우면 대부분 중복된 코드 이다.
- 이는 소프트웨어 공학의 DRY(don't repeat yourself) 원칙을 어기는 것이다.
2.1.3 세 번째 시도 : 가능한 모든 속성으로 필터링
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 && apple.getWeight() > weight)) {
result.add(apple);
}
}
return result;
}
- 위 코드의 boolean 타입의 flag의 true, false라는 의미를 정확하게 인지하기 힘들다.
- 여러 요구사항이 생기면, 중복된 메서드를 다시금 만들게 되는 문제가 발생한다.
동작 파라미터화
- 동작 파라미터화란 아직은 어떻게 실행할 것인지 결정하지 않은 코드 블록을 의미한다.
- 즉, 코드 블럭의 실행은 나중으로 미뤄진다.
- 결과적으로 코드 블럭에 따라 메서드의 동작이 파라미터화된다.
참 또는 거짓을 반환하는 함수를 프레디케이트라고 한다.
선택 조건을 결정하는 인터페이스를 정의하자
interface ApplePredicate {
boolean test(Apple apple);
}
다음은 다양한 선택 조건을 대표하는 여러 버전의 ApplePredicate를 정의할 수 있다.
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());
}
}
위 조건에 따라 filter 메서드가 다르게 동작할 것이라고 예상 이를 전략 디자인 패터(strategy design pattern)이라고 부른다.
filterApples에서 ApplePredicate 객체를 받아 애플의 조건을 검사하도록 메서드를 고쳐야 한다.
이렇게 동작 파라미터화, 즉 메서드를 다양한 동작(또는 전략)을 받아서 내부적으로 다양한 동작을 수행할 수 있다.
2.2.1 네 번째 시도 : 추상적 조건으로 필터링
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;
}
- 첫 번째 코드의 비해 유연한 코드를 얻었으며 가독성도 좋아지고 사용하기도 쉬워졌다.
150그램이 넘는 빨간 사과를 검색해달라는 요청이 오면 유연하게 코드 사용가능
public class AppleRedAndHeavyPredicate implements ApplePredicate {
public boolean test(Apple apple) {
return RED.equals(apple.getColor())
&& apple.getWeight() > 150;
}
}
우리가 전달한 ApplePredicate 객체에 의해 filterApples 메서드의 동작이 결정된다!
한 개의 파라미터, 다양한 동작
각 항목에 적용할 동작을 분리할 수 있다는 것이 동작 파라미터화의 강점이다.
[퀴즈 2-1] 유연한 prettyPrintApple 메서드 구현하기
사과 리스트를 인수로 받아 다양한 방법으로 문자열을 생성(커스터마이즈된 다양한 toString 메서드와 같이)할 수 있도록 파라미터화된 prettyPrintApple 메서드를 구현하시오.
에를 들어 prettyPrintApple 메서드가 각각의 사과 무게를 출력하도록 지시할 수 있다. 혹은 각각의 사과가 무거운지, 가벼운지 출력하도록 지시할 수 있다. prettyPrintApple 메서드는 지금까지 살펴본 필터링
예제와 비슷한 방법으로 구현할 수 잇다. 독자 여러분이 좀 더 쉽게 문제를 해결할 수 있도록 대략적인 코드를 공개한다.
public static void prettyPrintApple(List<Apple> inventory, ???) {
for(Apple apple: inventory) {
String output = ???.????(apple);
System.out.println(output);
}
}
정답
//interface 생성
public interface formatter() {
String accept(Apple a);
}
//다양한 AppleFormatter 생성
public class AppleFancyFormatter implements AppleFormatter {
public String accept(Apple apple) {
String characteristic = apple.getWeight() > 150 ? "heavy" : "light";
return "A " + characteristic + " " + apple.getColor() + " apple";
}
}
public class AppleSimpleFormatter implements AppleFormatter {
public String accept(Apple apple) {
retrun "An apple of " + apple.getWeight() + "g";
}
}
//사용
public static void prettyPrintApple(List<Apple> inventory, AppleFormatter formatter) {
for(Apple apple: inventory) {
String output = formatter.accept(apple);
System.out.println(output);
}
}
prettyPrintApple(inventory, new AppleFancyFormatter());
- A light green apple, A heavy red apple
prettyPrintApple(inventory, new AppleSimpleFormatter());
- An apple of 80g, An apple of 155g
복잡한 과정 간소화
filterApples 메서드로 새로운 동작을 전달하려면 ApplePredicate 인터페이스를 구현하는 여러 클래스를 정의한 다음에 인스턴스화해야 한다. 이는 상당히 번거로운 작업이며 시간 낭비다.
자바는 클래스의 선언과 인스턴스화를 동시에 수행할 수 있도록 익명 클래스(anonymous class)라는 기법을 제공한다.
익명 클래스
익명 클래스는 자바의 지역 클래스(local class)(블록 내부에 선언된 클래스)와 비슷한 개념이다.
익명 클래스는 말 그대로 이름이 없는 클래스다. 익명 클래스를 이용하면 클래스 선언과 인스턴스화를 동시에 할수 있다.
즉, 즉석에서 필요한 구현을 만들어서 사용할 수 있다.
2.3.2 다섯 번째 시도 : 익명 클래스 사용
List<Apple> redApples = filterApples(inventory, new ApplePredicate() {
public boolean test(Apple apple) {
return RED.equals(apple.getColor());
}
}
[퀴즈 2-1] 익명 클래스 문제
다음 코드를 실행한 결과는 4, 5, 6, 42중 어느 것일까?
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(); < 이행의 출력 결과는?
}
}
정답
코드에서 this는 MeaningOfThis가 아니라 Runnable을 참조하므로 5가 정답
2.3.3 여섯 번째 시도 : 람다 표현식 사용
List<Apple> result = filterApples(inventory, (Apple apple) -> RED.equals(apple.getColor())):
2.3.4 일곱 번째 시도 : 리스트 형식으로 추상화
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;
}
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());
Runnable로 코드 블록 실행하기
//java.lang.Runnable
public interface Runnable {
void run();
}
Thread t = new Thread(new Runnable() {
public void run() {
System.out.println("Happy World");
}
}
//람다방식
Thread t = new Thread(() -> System.out.println("Happy World"));
Callable을 결과로 반환하기
//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());
GUI 이벤트 처리하기
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!!"));
'Programming > Java' 카테고리의 다른 글
EPISODE 6. 모던 자바 인 액션(CAHPTER 6 스트림으로 데이터 수집) (0) | 2023.07.11 |
---|---|
EPISODE 5. 모던 자바 인 액션(CHAPTER 5 스트림 활용) (1) | 2023.07.10 |
EPISODE 4. 모던 자바 인 액션(CHAPTER 4 스트림 소개) (0) | 2023.07.07 |
EPISODE 3. 모던 자바 인 액션(CHAPTER 3 람다 표현식) (0) | 2023.07.06 |
EPISODE 1. 모던 자바 인 액션(CHAPTER 1 자바 8, 9, 10, 11 : 무슨 일이 일어나고 있는가?) (0) | 2023.07.04 |