Ian's Archive 🏃🏻

Profile

Ian

Ian's Archive

Developer / React, SpringBoot ...

📍 Korea
Github Profile →
Categories
All PostsAlgorithm19Book1C1CI/CD2Cloud3DB1DesignPattern9ELK4Engineering1Front3Gatsby2Git2IDE1JAVA7JPA5Java1Linux8Nginx1PHP2Python1React9Security4SpatialData1Spring26
thumbnail

Modern Java In Action 2장 정리

JAVA
2025.03.28.

Series

  • 1Modern Java In Action 1장 정리
  • 2Modern Java In Action 2장 정리
  • 3Modern Java In Action 3장 정리

이 장의 내용

동작 파라미터화는 변화하는 요구사항에 유연하게 대응할 수 있게 코드를 구현하는 방법 중 하나이다.

메서드 내부적으로 다양한 동작을 수행할 수 있도록 코드를 메서드 인수로 전달한다.

동작 파라미터화를 이용하면 다음과 같은 기능을 수행할 수 있다.

  • 리스트의 모든 요소에 대해서 어떤 동작을 수행할 수 있음
  • 리스트 관련 작업을 끝낸 다음에 어떤 다른 동작을 수행할 수 있음
  • 에러가 발생하면 정해진 어떤 다른 동작을 수행할 수 있음

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

기존 농장 재고목록 에플리케이션에 리스트에서 녹색 사과만 필터링하는 기능을 추가하는 상황을 가정한다.

2.1.1 첫 번째 시도 : 녹색 사과 필터링

사과 색을 정의하는 enum Color {RED, GREEN}이 존재한다고 가정하면

복사
public static List<Apple> filterGreenApples (List<Apple> intentory) {
  List<Apple> result = new ArrayList<>();
  for(Apple apple : inventory) {
    if (GREEN.equals(apple.getColor())) {
      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().equals(color)) {
      result.add(apple);
    }
  }
  return result;
}
복사
filterApplesByColor(inventory, GREEN);

filterApplesByColor(inventory, RED);

2.1.3 세 번째 시도 : 가능한 모든 속성을 필터링

복사
public static List<Apple> filterApple (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)

      ...

    )
  }
}

와 같은 코드는 모든 속성을 필터링 할 수 있지만 형편없고, 요구사항이 바뀔 때 마다 유연하게 대응할 수도 없다.

이제 이러한 상황에서 동작 파라미터화 를 이용해 유연성을 얻는 방법을 알아보자

2.2 동작 파라미터화

색상, 무게 등의 선택 조건을 사과의 어떤 속성에 기조해서 boolean값을 반환하는 방법이 있다.

참 또는 거짓을 반환하는 함수를 프레디케이트라고 한다. 선택조건을 결정하는 인터페이스를 정의하자

복사
public interface ApplePredicate {
  boolean test(Apple apple);
}
복사
// 무거운 사과만 선택
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());
  }
}

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

ApplePredicate가 알고리즘 패밀리고, AppleHeavyWeightPredicateAppleGreenColorPredicate가 전략이 되는 것이다.


이제 filterApples 메서드가 ApplePredicate객체를 인수로 받도록 코드를 수정해보자

2.2.1 네 번째 시도 : 추상적 조건으로 필터링

복사
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;
}
복사
public class AppleRedAndHeavyPredicate implements ApplePredicate {
  public boolean test (Apple apple){
    return RED.equals(apple.getColor()) && apple.getWeight() > 150;
  }
}

filterApples메서드의 동작을 파라미터화 하였더니 첫 번째 코드에 비해 더 유연하고 가독성 좋은 코드가 되었다.

이처럼 동작 파라미터화는 한 메서드가 다른 동작을 수행하도록 재활용할 수 있고, 이는 유연한 API를 만들 때 중요한 역할을 한다.

지금까지 동작을 추상화해서 변화하는 요구사항에 대응 가능한 코드를 작성하는 방법을 알아보았다.

이번엔 여러 클래스를 구현해서 인스턴스화 하는 과정을 좀 더 개선해보자

2.3 복잡한 과정 간소화

자바는 클래스의 선언과 인스턴스화를 동시에 수행할 수 있도록 익명 클래스 라는 기법을 제공한다.

익명 클래스란 말 그대로 이름이 없는 클래스이고, 이를 활용하면 즉석해서 필요한 구현을 만들어서 사용할 수 있다는 장점이 있다.

2.3.1 다섯 번째 시도 : 익명 클래스 사용

복사
List<Apple> redApples = filterApples(inventory, new ApplePredicate() {
  public boolean test(Apple apple){
    return RED.equals(apple.getColor());
  }
});

하지만 익명 클래스는 여전히 부족한 점이 있다.

  • 여전히 많은 공간을 차지한다는 것
  • 많은 프로그래머가 익명 클래스 사용에 익숙하지 않다는 것

익명 클래스로 코드를 조금 개선해볼 수는 있지만 여전히 만족스럽지 않다.

결국 객체를 만들고 명시적으로 새로운 동작을 정의하는 메서드를 구현해야 한다는 점은 변하지 않는다.

2.3.2 여섯 번째 시도 : 람다 표현식 사용

복사
List<Apple> result = filterApples(inventory, (Apple apple) -> RED.equals(apple.getColor()));

람다 표현식을 이용하면 코드가 훨씬 간결해지고 가독성이 좋아진다.

2.3.3 일곱 번째 시도 : 리스트 형식으로 추상화

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

코드를 리스트 형식으로 추상화하면 사과뿐 아니라 바나나, 오렌지, 정수, 문자열 등 다양한 타입에 대해 메소드가 적용 가능해진다.

복사
filter(inventory, (Apple apple) -> RED.equals(apple.getColor()));
filter(inventory, (Integer i) -> i % 2 == 0);

2.4 실전 예제

2.4.1 Comparator로 정렬하기

2.4.2 Runnable로 코드 블록 실행하기

2.4.3 Callable을 결과로 반환하기

Reference

모던 자바 인 액션

Previous Post
스프링 부트 핵심 원리와 활용 - 김영한 정리
Next Post
Modern Java In Action 3장 정리
Thank You for Visiting My Blog, I hope you have an amazing day 😆
© 2023 Ian, Powered By Gatsby.