[Java] 동작 파라미터화 코드 전달하기
동작 파라미터화?
동작 파라미터화란 아직은 어떻게 실행할 것인지 결정하지 않은 코드블록을 의미한다.
이 코드 블록은 나중에 프로그램에서 호출한다.
예를 들어, 나중에 실행될 메서드의 인수로 코드 블록을 전달할 수 있다. 결과적으로 코드 블록에 따라 메서드의 동작이 파라미터화 된다.
변화하는 요구사항마다 새로운 파라미터를 받는 메서드를 만들어야 하는 상황이 생길 수 있다. 하지만 동작 파라미터화 코드를 전달하면 해결이 가능하다.
변화하는 요구사항에 대응하기
농장 재고목록 애플리케이션에서 재고 파악하는 예제를 통해 요구사항의 변화에 대응해보자.
요구사항 1. 녹색 사과만 필터링
사과 색을 정의하는 다음과 같은 Color enum이 존재한다고 가정하자
enum Color { RED, GREEN }
public static List<Apple> filterGreenApples(List<Apple> inventory) {
List<Apple> result = new ArrayList<>();
for (Apple apple: inventory) {
if (GREEN.equals(apple.getColor())) {
result.add(apple);
}
}
return result;
}
녹색 사과만 필터링 해보았다.
그런데 갑자기 농부가 변심하여 녹색 사과 말고 빨간 사과도 필터링하고 싶어졌다. 어떻게 고쳐야 할까?
쉽게 해결하려면 새 메서드를 만들면 되겠지만, 더 다양한 색이 추가된다면 적절히 대응하기 힘들어진다.
이렇게 비슷한 코드가 반복되는 상황에서는 코드를 추상화하는게 좋다. 즉 여기선 색을 파라미터화 하여 해결할 수 있을 것이다.
요구사항 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;
}
이러면 이렇게 색을 파라미터화 하여 여러 색을 필터링할 수 있게 됐다.
그럼 이제 여기서 무게도 150 그램 이상으로 필터링 할 수 있도록 요구사항이 추가됐다고 해보자.
public static List<Apple> filterApplesByWeight(List<Apple> inventory, int weight) {
List<Apple> result = new ArrayList<>();
for (Apple apple: inventory) {
if (apple.getWeight() > weight) {
result.add(apple);
}
}
return result;
}
위 코드도 좋은 방법이긴 하지만, 색을 필터링 하는 코드와 위 코드가 대부분 중복된다. 이는 DRY 원칙을 어기는 것이다.
어떤 방법으로 해결할 수 있을까?
색과 무게를 filter라는 메서드로 합치는 방법도 있다. 하지만 이러면 어떤 기준으로 필터링 할지 구분하는 방법이 필요하다. (하지만 실전에선 절대 사용하면 안되는 방법이다)
요구사항 3. 가능한 모든 속성으로 필터링
public static List<Apple> filterApples(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;
}
다음처럼 위 메서드를 사용할 수 있다.
List<Apple> greenApples = filterApples(inventory, GREEN, 0, true);
List<Apple> heavyApples = filterApples(inventory, null, 150, false);
정말 형편없는 코드다. 매우 마음에 들지 않는다. 여기서 true와 false는 무엇을 의미하는 것일까? 게다가 요구사항이 변화했을 때 유연하게 대응하기도 힘들다.
결국 여러 중복된 필터 메서드를 만들거나 모든 것을 처리하는 하나의 필터 메서드를 구현해야 한다. 이보다 더 효과적으로 전달할 수 있는 방법이 없을까?
우리는 동작 파라미터화를 통해 문제를 해결할 수 있다.
true, false를 반환하는 함수를 Predicated라고 한다. 이 Predicate를 이용하여 선택 조건을 결정하는 인터페이스를 정의하자.
public interface ApplePredicate {
boolean test(Apple apple);
}
이는 전략 패턴과 일치시킬 수 있으며 우리는 Predicate라는 인터페이스를 파라미터로 활용하여 원하는 전략을 사용해 필터링할 수 있다.
public class AppleHeavyWeightPredicated implements ApplePredicate {
public boolean test(Apple apple) {
return apple.getWeight() > 150;
}
}
public class AppleGreenWeightPredicated implements ApplePredicate {
public boolean test(Apple apple) {
return GREEN.equals(apple.getColor());
}
}
요구사항 4. 추상적 조건으로 필터링
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;
}
위 코드보다 훨씬 유연하고 가독성 좋은 코드를 짤 수 있게 됐다. 이제 필요한 대로 다양한 ApplePredicate를 만들어서 filterApples 메서드로 전달할 수 있다.
예를 들어, 150g이 넘는 빨간 사과를 검색해달라고 하면 다음과 같이 클래스를 만들면 된다.
public class AppleRedAndHeavyPredicate implements ApplePredicate {
public boolean test(Apple apple) {
return RED.equals(apple.getColor()) && apple.getWeight() > 150;
}
}
List<Apple> redAndHeavyApples = filterApples(inventory, new AppleRedAndHeavyPredicate());
이렇게해서 filterApples 메서드의 동작을 파라미터화 했다.
마무리
지금까지 본 것 처럼 컬렉션 탐색 로직과 각 항목에 적용할 동작을 분리할 수 있다는 것이 동작 파라미터화의 강점이다.
이는 한 메서드가 다른 동작을 수행하도록 재활용할 수 있다. 따라서 유연한 API를 만들 때 동작 파라미터화가 중요한 역할을 한다.
'Language > Java' 카테고리의 다른 글
[Java] 함수형 인터페이스 (0) | 2023.04.03 |
---|---|
[Java] 람다 표현식 소개 (0) | 2023.03.31 |
[Java] 바이트코드 조작 (0) | 2023.03.22 |
[Java] JVM (0) | 2023.03.20 |
[Java] "" vs new String("") (2) | 2023.03.19 |
댓글
이 글 공유하기
다른 글
-
[Java] 함수형 인터페이스
[Java] 함수형 인터페이스
2023.04.03 -
[Java] 람다 표현식 소개
[Java] 람다 표현식 소개
2023.03.31 -
[Java] 바이트코드 조작
[Java] 바이트코드 조작
2023.03.22 -
[Java] JVM
[Java] JVM
2023.03.20