[Modern Java In Action] CH02. 동작 파라미터화

khyojun·2023년 7월 7일
2

Modern Java In Action

목록 보기
2/3
post-thumbnail

동작 파라미터화란?

아직은 어떻게 실행할 것인지 결정하지 않은 코드 블록을 의미한다. 메서드의 동작이 파라미터화가 된다.

사용하는 이유가 무엇일까?

여러가지 이유가 있겠지만 가장 핵심적으로 말씀해주시는 것은 변화하는 요구사항에 대응하기 위해서이다.

다음과 같은 예시 상황이 있다고 하자.

농부의 요구사항이다.
1. 사과가 초록색인지 확인해봐라~
2. 아니다. 초록색말고 빨강색인지도 확인해봐라.
3. 그 중에서 150그램 이상인 걸 찾아보자.

이러한 과정을 진행했을때 우리가 코드를 짠다면 다음과 같이 구성이 될 거다.


public static List<Apple> filterApples(List<Apple>![](https://velog.velcdn.com/images/nandong1104/post/aceab00c-33de-467d-aa1d-3d6a06458f52/image.gif)
 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;    
}


//호출 시

List<Apple> greenApples = filterApples(inventory, GREEN, 0, true);
List<Apple> heavyApples = filterApples(inventory, null, 150, false);

요구사항에는 잘 맞추긴 했지만 여기에서 갑자기 요구사항이 바뀌게 된다면? 약간 골치 아픈 상황이 생길 거 같다.

그래서 책에서 말하는 바는 다음과 같다. 이제부터 어떤 기준으로 사과를 필터링할 것인지 효과적으로 전달할 수 있게 해보자.


유연하게 대응하는 동적 파라미터화 진행하기

아까 위에서 요구사항을 우리는 알고 있지만 불안 할 거다. 언제 이 요구사항을 바꿔달라할 지 모르니 말이다.

그래서 책에서는 Predicate라는 것을 사용하기로 했다.

Predicate : 어떤 속성에 기초해서 불리언값을 반환(ex : 사과가 녹색인지? 사과가 150그램 이상인지?)하는 함수

그래서 다음과 같은 과정을 진행한다.

우선 선택 조건을 결정하는 인터페이스를 정의하자.


interface ApplePredicate {

    boolean test(Apple a);

  }

그리고 여러 버전의 Predicate를 만들어보자.


   public class AppleWeightPredicate implements ApplePredicate {

    @Override
    public boolean test(Apple apple) {
      return apple.getWeight() > 150;
    }

  }

  public class AppleColorPredicate implements ApplePredicate {

    @Override
    public boolean test(Apple apple) {
      return apple.getColor() == Color.GREEN;
    }

  }

사과 선택 전략에 따라 이렇게 filter가 동작하도록 만드는 것을 전략 디자인 패턴이라고도 부르기로 했다.

전략 디자인 패턴 : 각 알고리즘을 캡슐화하는 알고리즘 패밀리를 정의해둔 다음에 런타임에 알고리즘을 선택하는 기법이다.

우리의 코드에서 보면 AppleWeightPredicate, AppleColorPredicate가 전략이고 ApplePredicate가 알고리즘 패밀리다.

이렇게 까지 설계를 했으면 이제 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;
  }
  
  //호출 시
List<Apple> heavyApples = filterApples(inventory, new AppleWeightPredicate());

이런식으로 고치게 되면? ApplePredicate를 통해서 filter를 호출하게 될 때 더 동적으로 수행할 수 있을거다! 전.략 에 따라서 말이다.

우리가 호출하는 new AppleWeightPredicate() 이 친구를 통해서 우리가 메서드의 동작을 파라미터화 했다는 것을 알 수 있다.

그렇지만 아직 더 고칠게 남아있다. 불편하게 생각해보자.

위에서 우리가 굳이 이 Predicate하나를 위해서 클래스를 2개나 만드는 작업을 진행해야 했을까?

라는 생각을 해보자. 그러면 당연히 더 줄일 수 있으면 그런 방법을 찾을 거다.

이렇게 할 수 있는 방법 중 하나가 바로 익명 클래스다.

익명 클래스 : 이름이 없는 클래스, 클래스 선언과 인스턴스화를 동시에 할 수 있다.

이제는 호출과 동시에 바로 메서드의 동작을 파라미터화 해보자.

List<Apple> heavyApples = filterApples(inventory, new AppleWeightPredicate(){
    public boolean test(Apple apple){
        return apple.getWeight() > 150;
    }
    
    });

엄청 많이 줄어들었다. 그렇지만 아직도 길다고 책에서는 말하고 있다. 여기서 더 줄일 수 있는 방법이 있긴 한데 왜 이렇게 줄였는데 만족하지 못할까?

크게 2 가지의 이유가 있었다고 한다.

  1. 아직 많은 공간을 차지한다. (이후에 줄어들 코드를 보면 이해가 갈 것이다.)
  2. 아직 익명 클래스에 익숙하지가 않다.

그래서 이젠 더 줄여볼 것이다. 바로 람다라는 것을 사용해서 말이다.


람다

이제 자바 8의 장점인 람다라는 표현식을 사용해서 위 코드를 더 줄여보자.

List<Apple> heavyApple = (inventory, (Apple apple) -> apple.getWeight() > 150;)

그 길었고 유연하지 않았던 코드가 유연하면서 간결하게 한 줄로 표현이 되게 되었다.


요약

  • 동작 파라미터화에서는 메서드 내부적으로 다양한 동작을 수행할 수 있도록 코드를 파라미터로 전달한다.
  • 요구사항이 변화하더라도 더 잘 대응할 수 있는 코드를 구현할 수 있다.
  • 전략 디자인 패턴, 익명 클래스, 람다 를 활용해서 유연하게 요구사항을 대처하고 간결하게 코드를 작성할 수 있게 되었다!
profile
코드를 씹고 뜯고 맛보고 즐기는 것을 지향하는 개발자가 되고 싶습니다

0개의 댓글