Java 동작 파라미터화

Travel·2023년 8월 29일

Java

목록 보기
5/5

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

개발을 진행하다 보면 소비자의 요구사항은 항상 바뀐다.

예를 들어 농부가 재고 목록 조사를 쉽게 할 수 있도록 돕는 애플리케이션이 있다고 가정하자.

처음에는 녹색 사과를 모두 찾고 싶어요 라는 요구사항 하나만 있었는데 추후에 150 그램 이상인 사과를 찾고 싶어요라는 요구사항이 추가 됐다.

이렇게 시시가각 변하는 사용자의 요구사항에 어떻게 대응해야 할까?

여기서 동작 파라미터화를 이용하면 바뀌는 요구사항에 효과적으로 대응할 수 있다.

동작 파라미터화란?

아직은 어떻게 실행할 것인지 결정하지 앟은 코드 블록을 의미한다.

즉 코드 블록의 실행은 나증에 미루워진다.

예를 들어 컬렉션을 처리할 때 다음과 같은 메서드를 구현한다고 가정하자.

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

동작 파라미터화은 이처럼 다양한 기능을 수행할 수 있다.

동작 파라미터화 필요한 이유

  1. 농부가 녹색사과를 모두 찾고 싶다고한다.
public static List<Apple> filterGreenApple(List<Apple> inventory) {
        List<Apple> result =  new ArrayList<>();
        for (Apple apple : inventory) {
            if (Color.GREEN.equals(apple.getColor())) { //녹색 사과를 찾기위한 조건
                result.add(apple);
            }
        }
        
        return result;
    }
  1. 만약 여기서 빨간 색의 사과를 찾고 싶다는 요구사항이 추가된다면?

    크게 생각하지 않고 위에 메서드를 복사해서 컬러부분만 레드로 바꾸는 방법도 있을 수 있지만 코드를 반복 사용하지 않고 구현을 하는 방법은 색을 파라미터화 하는 것이다.
public static List<Apple> filteAppleByColor(List<Apple> inventory, Color color) {
        List<Apple> result =  new ArrayList<>();
        for (Apple apple : inventory) {
            if (color.equals(apple.getColor())) { // 특정 색의 사과를 찾기위한 조건
                result.add(apple);
            }
        }

        return result;
    }

이렇게 작성하면 요구사항에 유연하게 대응할 수 있을 것이다.

  1. 그런데 갑자기 농부가 무게가 150그램 이상인 사과를 구분하고 싶다고 해서 filterAppleByColor와 같이 무게를 파라미터화 시켜 필터를 만들었다.
public static List<Apple> filteAppleByWeight(List<Apple> inventory, int weight) {
        List<Apple> result =  new ArrayList<>();
        for (Apple apple : inventory) {
            if (apple.getWeight() >= weight) { // 특정 무게 이상의 사과를 찾기위한 조건
                result.add(apple);
            }
        }

        return result;
    }

위 코드도 좋은 해결책이라고 볼 수 있지만, 색과 무게를 필터하는 코드들이 대부분 중복된다는 것을 알 수 있다.

이는 소프트웨어 공학의 DRY(Don't Repeat Yourself) 원칙을 어기는 것이다.

동작 파라미터화

Predicate 사용하기

전체를 봤을 때 요구사항에서 선택 조건을 다음처럼 결정할 수 있다.

사과의 어떤 속성에 기초해서 불리언값을 반화하는 방법

(Argument를 받아 boolean값을 반환하는 함수를 Predicate라고 한다)

선택 조건을 결정하는 인터페이스와 클래스를 아래와 같이 정의할 수 있다.

public interface ApplePredicate {
    boolean test(Apple apple);
}

public class AppleGreenColorPredicate implements ApplePredicate{

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


public class AppleHeavyWeightPredicate implements ApplePredicate{

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

이제 filterApples 메서드가 ApplePredicate객체를 인수로 받도록 고치자. 이렇게 하면 filterApples 메서드 내부에서 컬렉션을 반복하는 로직과 컬렉션의 각 요소에 적용할 동작을 분리할 수 있다.

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

익명클래스 사용하기

첫번째 코드에 비해 더 유연한 코드와 동시에 가독성 좋은 코드로 변한걸 확인할 수 있다.

지금까지 살펴본 것처럼 컬렉션 탐색 로직과 각 항목에 적용할 동작을 분리할 수 있나는 것이 동작 파라미터화의 장점이다.

그러나 이처럼 작성하게 되면 로직과 관련 없는 코드가 많이 추가된다.

이를 해결하기 위한 방법으로 자바가 제공하는 익명 클래스라는 기법이 있다.

다음은 익명 클래스를 이용해서 ApplePredicate를 구현하는 객체를 만드는 방법으로 필터링 예제를 다시 구현한 코드이다.

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

익명 클래스는 아직 부족한 점이 있다.
1. 많은 공간을 차지한다.
2. 많은 프로그래머가 익명 클래스의 사용에 익숙하지 않다.

익명 클래스로 인터페이스를 구현하는 여러 클래스를 선언하는 과정을 조금 줄이긴 했지만 여전히 만족스럽지 않다.

Lambda 사용하기

이제 자바8의 람다 표현식을 이용해서 어떻게 코드를 간결하게 정리할 수 있는지 간단히 살펴보자.

List<Apple> redApples =
        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;
}

T 제네릭

클래스/인터페이스/메서드 등의 타입을 파라미터로 사용할 수 있게 해주는 역할을 한다.

이제 바나나, 오렌지, 정수, 문자열 등의 리스트에 필터메서드를 사용할 수 있다.

List<Apple> redApples =
        filter(inventory, (Apple apple) -> RED.equals(apple.getColor()));
List<Integer> evenNumbers =
        filter(numbers, (Integer i) -> i % 2 == 0);

출처

모던 자바 인 액션

0개의 댓글