참고
요구사항 변화에 대응하기
NOTE
예제 코드를 개선하면서 유연한 코드를 만드는 작업을 진행한다!
1. 녹색 사과 필터링(단순 조건)
NOTE
enum Color { RED, GREEN }
public List<Apple> filterGreenApples(List<Apple> inventory) {
final var result = new ArrayList<Apple>();
for (final var apple : inventory) {
if (GREEN.equals(apple.color())) {
result.add(apple);
}
}
return result;
}
Java
복사
녹색 사과를 필터링하는 것!
•
현재의 요구사항은 녹색 사과만 필터링하는 것이지만 빨간색 사과가 요구사항으로 올 수 있다.
•
이럴 경우 위의 코드와 유사하게 작성될 것이고, 코드가 반복된다.
2. 색을 파라미터화(1개 파라미터 조건)
NOTE
// 색을 파라미터화
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;
}
Java
복사
다양한 색상을 필터링하기위해 파라미터로 색상을 받는다!
•
여기서 색 이외에 무게를 이용해서 사과를 필터링하고 싶다는 요구사항이 추가되면 어떻게 해야할까?
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;
}
Java
복사
색과 마찬가지로 무게를 파라미터로 넣어준다.
•
하지만 색, 무게를 각각 파라미터화해서 조건에 적용한 것외에는 대부분 중복되는 코드다.
•
이는 소프트웨어 공학의 DRY(do not repeat yourself) 원칙을 어기는 것이다.
3. 가능한 모든 속성으로 필터링
NOTE
public List<Apple> filterApples(List<Apple> inventory, Color color, int weight, boolean flag) {
final var result = new ArrayList<Apple>();
for (final var apple : inventory) {
if ((flag && apple.color().equals(color)) ||
(!flag && apple.weight() > weight)) {
result.add(apple);
}
}
return result;
}
Java
복사
색, 무게를 모두 파라미터에 넣는다. (flas를 통해 어떤걸로 필터링할지 결정)
List<Apple> greenApples = filterApples(inventory, GREEN, 0, true);
List<Apple> heavyApples = filterApples(inventory, null, 150, false);
Java
복사
구현은 했지만 처음볼때 true와 false는 뭘의미하는지 알 수 있을까?
•
색과 무게는 필터링이 가능하지만, 결국 또 다른 속성이 부여된다면 현재 코드를 수정해야한다.
•
심지어 빨간색 사과 중에 무거운 사과를 필터링하고 싶다면? 결국 중복된 메서드가 계속 생긴다.
동작 파라미터화
NOTE
동적 파라미터 ⇒ 아직은 어떻게 실행할 것인지 결정하지 않은 코드 블럭을 의미한다!
동작 파리머터화의 3가지 요소를 사용하면 유연함이 올라간다.
ApplePredicate는 어떤걸 구현했냐에 따라 동작이 달라짐!
// 인터페이스
public interface ApplePredicate {
boolean test(Apple apple);
}
// 인터페이스 구현체
public class AppleHeavyWeightPredicate implements ApplePredicate {
@Override
public boolean test(Apple apple) {
return apple.weight() > 200;
}
}
public class AppleGreenColorPredicate implements ApplePredicate {
@Override
public boolean test(Apple apple) {
return GREEN.equals(apple.color());
}
}
Java
복사
•
위와 같은 패턴을 전략 디자인 패턴이라고 부른다!
◦
런타임에 어떤게 실행될지 선택하는 기법.
4. 추상적 조건으로 필터링 (동작 파라미터화의 장점)
NOTE
public List<Apple> filterApples(List<Apple> inventory, ApplePredicate applePredicate) {
final var result = new ArrayList<Apple>();
for (final var apple : inventory) {
if (applePredicate.test(apple)) {
result.add(apple);
}
}
return result;
}
Java
복사
1번째 코드에 비해 더 유연한 코드를 얻었으며, 가독성도 좋아졌다!
각 항목에 적용할 동작을 분리할 수 있다는 것이 동작 파라미터화의 장점!
복잡한 과정 간소화
NOTE
지금까지의 작업은 코드의 유연성은 올라갔지만, 새로 구현해야하는 코드가 좀 많아졌다..
// 인터페이스
public interface ApplePredicate {
boolean test(Apple apple);
}
// 인터페이스 구현체
public class AppleHeavyWeightPredicate implements ApplePredicate {
@Override
public boolean test(Apple apple) { // test 선언이 중복됨..
return apple.weight() > 200;
}
}
public class AppleGreenColorPredicate implements ApplePredicate {
@Override
public boolean test(Apple apple) { // test 선언이 중복됨..
return GREEN.equals(apple.color());
}
}
Java
복사
다음과 같은 코드들을 더 간소화 하는 방법은 없을까?
5. 익명스를 통한 동작 파라미터화
NOTE
익명클래스 ⇒ 클래스의 선언과 인스턴스화를 동시에 수행할 수 있다!
List<Apple> redApples = filterApples(inventory, new ApplePredicate() {
public boolean test(Apple a) {
return Color.RED.equals(a.getColor());
}
});
Java
복사
익명클래스를 사용해서 단축한 코드!
•
하지만 익명 클래스도 많은 공간을 차지한다.
•
많은 프로그래머가 사용하기에 익숙하지 않다.
6. 람다를 통한 동작 파라미터화
NOTE
람다 ⇒ 자바 8에서 생긴 간단한 코드 전달 기법!
List<Apple> result = filterApples(inventoruy, (Apple apple) -> RED.equals(apple.getColor()));
Java
복사
람다식 활용
•
람다를 인자값으로 넘기면 가장 짧게 구현됨
7. 제네릭으로 리스입 추상화
NOTE
제네릭으로 타입을 선언해서 다양한 유형의 데이터를 받을 수 있다!
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;
}
Java
복사
List<Apple> redApples = filter(inventory, (Apple apple) -> RED.equals(apple.getColor()));
List<Apple> evenNumbers = filter(numbers, (Integer i) -> i % 2 == 0);
Java
복사
바나나, 오렌지, 정수, 문자열 등의 리스트에 필터 메서드를 사용할 수 있다!
•
이렇게 해서 유연성과 간결함이라는 두 마리 토끼를 모두 잡을 수 있었다!
실전 예제
1. Comparator로 정렬하기
NOTE
자바 8의 List에는 sort메서드가 포함되어 있으며, Comparator 객체를 이용해서 sort의 동작을 파라미터화 할 수 있다!
inventory.sort(new Comparator<Apple>() {
public int compare(Apple o1, Apple o2) {
return o1.getWeight().compareTo(o2.getWeight());
}
});
Java
복사
inventory.sort((Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()));
Java
복사
2. Runnable로 코드 블록 실행하기
NOTE
// java.lang.Runnable
public interface Runnable {
void run();
}
Java
복사
// 익명 클래스
Thread t = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Hello World");
}
});
// 람다 클래스
Thread t2 = new Thread(() -> System.out.println("Hello World"));
Java
복사
익명클래스와 람다를 통한 Runnable 사용