동작 파라미터화(Behavior Parameterization 또는 행위 매개변수)란
아직 어떻게 실행할 것인지 결정하지 않은 코드블록을 의미한다.
이 코드블록은 나중에 프로그램에서 호출하며 즉, 코드 블록 실행은 나중으로 미뤄진다.
코드 블록에 메서드의 동작이 파라미터화 된다.
동작을 파라미터화 하므로서 변화하는 요구사항에 보다 효율적으로 대응 가능해진다.
다음은 예제와 그 과정이다.
첫 번째 시도 : 녹색 사과 필터링
녹색 사과를 필터링 해달라는 요청이 있어 아래와 같이 구현하였다.
기능은 동작하지만 아래 로직으로는 오로지 녹색 사과만 필터링할 수 있다.
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;
}
두 번째 시도 : 색을 파라미터화
빨간 사과도 필터링 되게 해달라는 추가 요청이 왔다.
filterGreenApples를 복사해서 filterRedApples를 추가할까 했지만,
좀 더 유연하게 대응하기 위해 색상을 파라미터화 하여 다른 색상도 적용 가능하게 만들었다.
public static List<Apple> filterApplesByColor(List<Apple> inventory, Color color) {
List<Apple> result = new ArrayList<>(); // 사과 누적 리스트
for (Apple apple : inventory) {
if (color.equals(apple.getColor())) { // color를 파라미터로 받아와 비교
result.add(apple);
}
}
return result;
}
색상 조건을 완료하였는데 색 이외에 무거운 사과도 필터링해달라는 요청이 들어왔다.
filterApplesByColor을 활용하여 filterApplesByWeight도 만들었다. 역시 무게값을 파라미터화 하였다.
public static List<Apple> filterApplesByWeight(List<Apple> inventory, int weigth) {
List<Apple> result = new ArrayList<>(); // 사과 누적 리스트
for (Apple apple : inventory) {
if (apple.getWeight() > weigth) { // 무게를 파라미터로 받아와 비교
result.add(apple);
}
}
return result;
}
좋은 해결책인듯 했지만, 두 메서드가 if조건 외에는 대부분 중복된다.
이는 소프트웨어 공학의 DRY(Don't Repeat Yourself, 같은 것을 반복하지 말 것) 원칙을 어기는 것이다.
세 번째 시도 : 가능한 모든 속성으로 필터링
중복 제거를 위해 두 메소드를 합치기로 했다.
무게, 색상을 모두 파라미터로 받아오고 추가로 무게 조건과 색상 조건을 구분하기 위해 Flag라는 파라미터도 받아오게 만들었다.
public static List<Apple> filterApples(List<Apple> inventory, Color color,
int weigth, boolean flag) {
List<Apple> result = new ArrayList<>();
for (Apple apple : inventory) {
if ((flag && color.equals(apple.getColor())) || // flag true면 색상 비교
(!flag && apple.getWeight() > weigth)) { // flag false면 무게 비교
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 결과였다.
조건을 파라미터화 하기엔 파라미터가 너무 많아지니, true/false를 반환하는 동작을 파라미터화 하기로 하였다.
우선, true/false를 반환하는 Predicate 함수를 Interface 로 정의하기로 했다.
public interface ApplePredicate {
boolean filter (Apple apple);
}
그리고 여러 버전의 ApplePredicate 구현체를 만들었다.
이제 filter 메서드는 실행되는 조건에 따라 다르게 동작할 수 있게 되었다.
public class AppleGreenColorPredicate implements ApplePredicate {
@Override
public boolean filter(Apple apple) {
return GREEN.equals(apple.getColor());
}
}
public class AppleHeavyWeightPredicate implements ApplePredicate {
@Override
public boolean filter(Apple apple) {
return apple.getWeight() > 150;
}
}
이와 같은 패턴을 전략 디자인 패턴(Strategy Design Pattern) 이라고 한다.
전략 디자인 패턴은 각 알고리즘(=전략)을 캡슐화하는 알고리즘 패밀리를 정의해둔 후, 런타임에 알고리즘을 선택하는 기법이다.
ApplePredicate이 알고리즘 패밀리이고, AppleGreenColorPredicate 와 AppleHeavyWeightPredicate이 전략이다.
filterApples 메서드는 ApplePredicate 객체를 인수로 받게 고치고, 상황에 따라 다양한 조건을 필터링할 수 있게 만들었다.
public static List<Apple> filterApples(List<Apple> inventory, ApplePredicate p) {
List<Apple> result = new ArrayList<>();
for (Apple apple : inventory) {
if (p.filter(apple)) { // Predicate 객체로 사과 필터링 조건을 캡슐화
result.add(apple);
}
}
return result;
}
동작을 파라미터로 받아와 유연한 대응이 가능해지긴 했지만,
조건이 추가될 때마다 filter 메서드를 감싸고 있는 ApplePredicate 객체가 계속 추가되어야 했다.
다섯 번째 시도 : 익명 클래스 사용
익명클래스란 말 그대로 이름없는 클래스이다.
익명 클래스를 이용하면 클래스 선언과 인스턴스화를 동시에 할 수 있다. 즉, 즉석에서 필요한 구현을 만들 수 있다.
새로운 요구사항인 빨간색 사과 필터링 조건은 익명클래스를 사용해보기로 했다.
List<Apple> redApples = filterApples(inventory, new ApplePredicate() { // 메서드 동작을 직접 파라미터화
@Override
public boolean filter(Apple apple) {
return RED.equals(apple.getColor()); // 빨간색 사과 필터링
}
});
하지만, 익명 클래스로도 아직 부족한 점이 있다.
다른 조건도 다 익명클래스로 구현하고 나니 filter의 return문 외에 다른 코드들이 대부분 중복된다.
여섯 번째 시도 : 람다 표현식 사용
람다 표현식을 사용하여 익명 클래스를 좀 더 간단하게 재구현하였다.
List<Apple> greenApples = filterApples(inventory, (Apple apple) -> GREEN.equals(apple.getColor()));
List<Apple> heavyApples = filterApples(inventory, (Apple apple) -> apple.getWeight() > 150);
List<Apple> redApples = filterApples(inventory, (Apple apple) -> RED.equals(apple.getColor()));
이젠 중복없이 간결한 코드로 다양한 조건을 처리할 수 있게 되었다!
일곱 번째 시도 : 리스트 형식으로 추상화
조금 더 나아가서, 제네릭을 이용해 Apple 외에 다른 객체들도 필터링할 수 있게 만들어보자
public interface Predicate<T> {
boolean filter (T t);
}
public static <T> List<T> filter(List<T> inventory, Predicate<T> p) {
List<T> result = new ArrayList<>();
for (T e : inventory) {
if (p.filter(e)) {
result.add(e);
}
}
return result;
}
List<Apple> greenApples = filter(inventory, (Apple apple) -> GREEN.equals(apple.getColor()));
List<Integer> evenNumbers = filter(numbers, (Integer i) -> i%2 == 0);
마치며
- 동작 파라미터화에서는 다양한 동작 수행을 할 수 있도록 메서드를 인수로 전달한다.
- 동작 파라미터화를 사용하면 요구사항에 유연하게 대응 가능하고, 나중에 드는 엔지리어링 비용도 줄일 수 있다.
- 람다와 같은 코드 전달 기법을 이용하면 동작을 메서드 인수로 전달할 수있다. (여러 클래스를 구현하는 수고로움을 덜 수 있다.)
- 자바 API의 많은 메서드는 정렬, 스레드, GUI 처리 등을 포함한 다양한 동작으로 파라미터화 할 수 있다.
'Book > 모던 자바 인 액션' 카테고리의 다른 글
[정리] 모던 자바 인 액션 - 스트림 활용(2) (0) | 2023.01.08 |
---|---|
[정리] 모던 자바 인 액션 - 스트림 활용(1) (2) | 2023.01.07 |
[정리] 모던 자바 인 액션 - 스트림 소개 (0) | 2023.01.06 |
[정리] 모던 자바 인 액션 - 람다 표현식 (Lambda Expression)(2) (0) | 2023.01.05 |
[정리] 모던 자바 인 액션 - 람다 표현식 (Lambda Expression)(1) (0) | 2023.01.04 |
댓글