티스토리 뷰

728x90
반응형

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


  • 동작 파라미터화란 아직은 어떻게 실행할 것인지 결정하지 않은 코드 블럭을 의미합니다. 다른 말로는 행위 매개변수화 코드라고도 합니다.

 

💡 변하는 부분과 변하지 않는 부분을 분리

  • 소프트웨어 주요 설계 원칙 중 하나는 변하는 부분과 변하지 않는 부분을 잘 캐치하여 분리하는 것입니다.
  • 동작 파라미터화를 사용하면 변하는 부분과 변하지 않는 부분을 분리함으로써 유동적인 요구사항에 대해 효과적으로 대처할 수 있습니다.

 

💡 변화하는 요구사항 - 빨간 사과 필터링

  • 첫번째 요구사항에서 사과 목록 중 빨간색 사과만 필터링하는 기능을 요구한 경우는 아래처럼 하면 가능하지만 추후 요구사항이 변경되어 녹색 사과만 필터링 해달라는 요청이 들어오게 된다면 또 다른 메서드를 구현해야 합니다.
public enum Color {
    RED, GREEN
}

@Getter
@AllArgsConstructor
public class Apple {

    private Color color;
    private int weight;
}

public class Main {

    public static void main(String[] args) {
        List<Apple> appleList = List.of(new Apple(Color.RED, 150), new Apple(Color.GREEN, 100));

        List<Apple> redAppleList = filterRedApple(appleList);
    }

    // 빨간 사과 목록을 반환하는 함수
    public static List<Apple> filterRedApple(List<Apple> appleList) {
        List<Apple> result = new ArrayList<>();

        for (Apple apple : appleList) {
            if (Color.RED == apple.getColor()) {
                result.add(apple);
            }
        }
        return result;
    }
    
    // 녹색 사과 목록을 반환하는 함수
    public static List<Apple> filterGreenApple(List<Apple> appleList) {
        List<Apple> result = new ArrayList<>();

        for (Apple apple : appleList) {
            if (Color.GREEN == apple.getColor()) {
                result.add(apple);
            }
        }
        return result;
    }
}

 

💡 유연한 대처 방법 1 - 색을 매개변수로 받기

  • 색을 매개변수로 받게 된다면 코드의 중복을 제거할 수 있고 유연하게 대처할 수 있습니다.
public class Main {

    public static void main(String[] args) {
        List<Apple> appleList = List.of(new Apple(Color.RED, 150), new Apple(Color.GREEN, 100));

        List<Apple> redAppleList = filterAppleByColor(appleList, Color.RED);
    }

    public static List<Apple> filterAppleByColor(List<Apple> appleList, Color color) {
        List<Apple> result = new ArrayList<>();

        for (Apple apple : appleList) {
            if (color == apple.getColor()) {
                result.add(apple);
            }
        }
        return result;
    }
}

 

💡 유연한 대처 방법 2 - 동작 파라미터화

  • boolean을 반환하는 Predicate 인터페이스를 만들어 각 구현체마다 동작하는 방법을 다르게하여 유연하게 상황을 대처할 수 있도록 합니다.
  • 이러한 방법을 전략 디자인 패턴이라 하는데 전략 디자인 패턴이란 전략을 캡슐화하는 전략 패밀리를 정의한 후 런타임에 상황에 맞는 전략을 취할 수 있는 기법입니다. 여기서는 ApplePredicate가 전략 패밀리에 속하고 Green, Red, Weight Predicate 클래스가 각 상황에 맞는 전략입니다.
  • 하지만 다양한 전략을 위해서는 각 상황에 맞는 클래스를 선언해야 하는데 전략이 많을수록 클래스의 수가 증가할 수 있습니다.
public interface ApplePredicate {

    boolean test(Apple apple);
}

// 녹색 사과를 필터링하는 Predicate
public class AppleGreenColorPredicate implements ApplePredicate {

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

// 빨간 사과를 필터링하는 Predicate
public class AppleRedColorPredicate implements ApplePredicate {

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

// 무거운 사과를 필터링하는 Predicate
public class AppleHeavyWeightPredicate implements ApplePredicate {

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

public class Main {

    public static void main(String[] args) {
        List<Apple> appleList = List.of(new Apple(Color.RED, 150), new Apple(Color.GREEN, 100));

        List<Apple> result = filterByApplePredicate(appleList, new AppleGreenColorPredicate());
    }

    public static List<Apple> filterByApplePredicate(List<Apple> inventory, ApplePredicate applePredicate) {
        List<Apple> result = new ArrayList<>();

        for (Apple apple : inventory) {
          if (applePredicate.test(apple)) {
              result.add(apple);
          }
        }
        return result;
    }
}

 

💡 유연한 대처 방법 3 - 익명 클래스 사용

  • 익명 클래스를 사용하면 클래스 선언과 인스턴스를 동시에 할 수 있습니다. 하지만 코드가 길어질 수 있습니다. 또한 위에서 언급한 클래스의 수가 많아짐을 방지할 수 있으며 java.util.Objects.Predicate 클래스를 사용하면 됩니다.
public class Main {

    public static void main(String[] args) {
        List<Apple> appleList = List.of(new Apple(Color.RED, 150), new Apple(Color.GREEN, 100));

        List<Apple> result = filterByAnonymousClass(appleList, new Predicate<Apple>() {
            @Override
            public boolean test(Apple apple) {
                return Color.GREEN == apple.getColor();
            }
        });
    }

    public static List<Apple> filterByAnonymousClass(List<Apple> appleList, Predicate<Apple> p) {
        List<Apple> result = new ArrayList<>();

        for (Apple apple : appleList) {
            if (p.test(apple)) {
                result.add(apple);
            }
        }
        return result;
    }
}

 

💡 유연한 대처 방법 4 - 람다 표현식 사용

  • 람다 표현식을 사용하면 코드가 간결해지고 가독성을 높일 수 있습니다.
public class Main {

    public static void main(String[] args) {
        List<Apple> appleList = List.of(new Apple(Color.RED, 150), new Apple(Color.GREEN, 100));

        List<Apple> result = filterByLambda(appleList, (Apple apple) -> Color.RED == apple.getColor());
    }

    public static List<Apple> filterByLambda(List<Apple> appleList, Predicate<Apple> p) {
        List<Apple> result = new ArrayList<>();

        for (Apple apple : appleList) {
            if (p.test(apple)) {
                result.add(apple);
            }
        }
        return result;
    }
}

 

💡 유연한 대처 방법 5 - 리스트 형식으로 추상화

  • 마지막으로 다양한 상황에서 필터링된 데이터를 List에 담기위하여 리스트 형식을 추상화할 수 있습니다.
public class Main {

    public static void main(String[] args) {
        List<Apple> appleList = List.of(new Apple(Color.RED, 150), new Apple(Color.GREEN, 100));
        List<String> stringList = List.of("Hello", "Java", "Hi");
        List<Integer> integerList = List.of(10, 20, 30, 40, 50);

        List<Apple> redAppleList = filter(appleList, (Apple apple) -> Color.RED == apple.getColor());
        List<String> stringResultList = filter(stringList, (String s) -> s.startsWith("H"));
        List<Integer> integerResultList = filter(integerList, (Integer i) -> i > 30);
        
        redAppleList.forEach(System.out::println);       // Apple(color=RED, weight=150)
        stringResultList.forEach(System.out::println);   // Hello, Hi
        integerResultList.forEach(System.out::println);  // 40, 50
    }
    
    public static <T> List<T> filter(List<T> list, Predicate<T> p) {
        List<T> result = new ArrayList<>();
        for (T t : list) {
            if (p.test(t)) {
                result.add(t);
            }
        }
        return result;
    }
}

 

✔️ 정리

  • 동작 파라미터화란 메서드 내부적으로 다양한 동작을 수행할 수 있도록 코드를 메서드의 매개변수로 전달하는 것을 말합니다.
  • 동작 파라미터화를 사용하면 유동적인 요구사항에 유연하게 대응할 수 있으며, 엔지니어링 비용을 줄일 수 있습니다.
  • 변하는 것과 변하지 않는 부분을 파악하여 분리함으로써 유연하게 코드를 작성할 수 있습니다.

 

 

 

 

 

 

728x90
반응형