티스토리 뷰

Chapter 2 - 동작 파라미터화 코드 전달하기

Chapter 1에 비해 간결하게 정리해 보려고 한다.

 

전에는 읽으면서 정리했지만 이번엔 다  읽고 정리.(정리하는 기준이 챕터랑 다를 수 있다.)


변화하는 요구사항에 대응하기

어플리케이션에서 자주 바뀌는 소비자 요구사항은 피할 수 없는 문제다. 어떻게 대응할 것인가?

 

유지보수 관점 + 엔지니어링 비용이 최소화되는 코드를 작성해야한다.

 

동작 파라미터 구현으로 위 문제를 해결 할 수 있다.

 

동작 파라미터란?

아직은 어떻게 실행할 것인지 결정하지 않은 코드 블록을 의미한다.
동작 파라미터에 조건에 맞는 동작을 구현함으로써, 요구사항에 유연하게 대응할 수 있게 됐다.
다만, 불필요한 코드가 늘어나기 때문에 람다 표현식으로 변환할 필요가 있다.

고전적 방식(Java 8 이전)의 구현법

동작 파리미터 구현 및 기존 코드와 비교를 위해, 예를 하나 들었다.

농부가 아래와 같이 요구사항을 계속 변경한다.

사과를 분류 해주세요. 150g을 기준으로 크고 작은 것으로요.
빨간 것만 분류 해주세요.
아니 초록색이 나을 것 같네요.
앗. 원산지도 추가해주세요.
생각해보니 150g은 기준이 애매해요. 200g으로 해주세요.

 

위와 같은 일은 실제 개발에서 너무나 흔히 일어나는 일이다.

 

2장 동작 파라미터 구현에서는 위에 대한 대응으로, 고전적인 리스트에서 반복문과 조건문을 통해 구현 방법을 소개 했다.

하나만 따오면,

public static List<Apple> filterApplesByWeight(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;
  }

위 코드가 요구사항 변경에 유연하게 대응이 가능한가? 를 그렇다고 말할 수 있겠지만, 파라마터의 의미도 불분명하고 함수의 네이밍도 마음에 들지 않는다.

 

거기다 조건문에 어떤 것으로 분류할 것인가를 결정하는 flag 값이 매우 거슬린다.
(책에서도 실전에선 절대 사용하지 말라고 강조함 ㅋㅋ)

 

JAVA 8에서는 위 문제에 대응하기 위해, 매서드의 파라미터로 함수를 전달하는 방식을 제안한다. 동작을 함수로 만들어 파라미터로 넘긴다. 이게 동작 파라미터화이다.


동작 파라미터 화

다음부터는 어떻게 동작 파라미터를 구현 하나를 설명한다. 함수형인터페이스 중 하나인 Predicate를 이용해 구현한다.

 

Predicate는 test라는 미구현 매서드가 내장되어 있고, true/false를 리턴한다.

(여기선 java에서 제공하는 Predicate 객체를 implement 하진 않았다.)

 

코드를 많이 넣고 싶지 않았는데, 여기서부터는 내용이 너무 중요해서 정리하고 넘어간다.

 

인터페이스 정의

public class 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 apple.getColor() == Color.GREEN;
    }
 }

위 조건에 따라 filter 메서드가 다르게 동작할 것이라고 예상할 수 있다. 이를전략 디자인패턴(strategy design patter)이라고 부른다.

 

전략 디자인 패턴은 각 알고리즘(전략이라 부르는)을 캡슐화 하는 알고리즘 패밀리를 정의해둔 다음에 런타임에 알고리즘을 선택하는 기법이다.
여기서는 ApplePredicate가 알고리즘 패밀리이며, 이를 구현한 클래스들이 전략들이다.

 

조건에 맞게 코드 통합

public class AppleRedAndHeavyPredicate implements ApplePredicate {
  public boolean test(Apple apple) {
    return RED.equals(apple.getColor()) && apple.getWeight() > 150;
  }
}

중간 최종 코드

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;
}

이제 여기서부터 코드 간소화 작업이 들어간다.

 

익명 클래스로 전환

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()));
List<Integer> evenNumbers = filter(numbers, (Integer i) -> i % 2 == 0);

개인적으로 람다를 이용해 동작 파라미터를 넘기는건 굉장히 신선했다.(사실 알고는 있었지만 실전에서 사용하려고 생각해보지 못했던 것들이다.)


실전 예제

Comparator

public interface Comparator<T> {
  int compare(T o1, T o2);
}

// 무게가 적은 순서로 목록에서 사과를 정렬
inventory.sort(new Comparator<Apple>() {
    public int comapre(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("hello world");
  }
});

// 람다로 변경
Thread t = new Thread(() -> System.out.println("Hello world"));

Callable

// java.util.concurrent.Callable
public interface Callabble<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());

 

마치며

이래서 책을 읽는구나 느꼈던 장. 파편적으로 알고 있던 자바에 대한 지식들이 모양을 잡아가는 느낌을 받았다.

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/10   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
글 보관함