Search
Duplicate
📒

[Java Study] 10-x. 동작 파라미터화 코드 전달하기

상태
미진행
수업
Java Study
주제
Stream
연관 노트
3 more properties
참고

요구사항 변화에 대응하기

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 사용