Modern Java Ch02. 동적 파라미터화 코드 전달하기
동적 파라미터화를 이용하면 자주 바뀌는 요구사항에 효과적으로 대응할 수 있다.
동적 파라미터화란 아직은 어떻게 실행할 것인지 결정하지 않은 코드 블록을 의미.
이 코드 블록은 나중에 프로그램에서 호출, 코드 블록의 실행은 나중으로 미뤄진다.
예를 들면 나중에 실행될 메서드의 인수로 코드 블록 전달.
변화하는 요구사항에 대응하기
하나의 예제를 선정한 다음 예제 코드를 점차 개선해 보자.
기존의 농장 재고목록 애플리케이션에 리스트에서 녹색 사과만 필터링하는 기능을 추가한다고 가정한다.
- 첫번째 시도.
public class Apple {
private Color color;
private int weight;
public Color getColor() {
return color;
}
public void setColor(Color color) {
this.color = color;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
}
public enum Color{ RED, GREEN}
public class Filtering {
public static List<Apple> filterGreenApples(List<Apple> inventory) {
List<Apple> result = new ArrayList<>();
for (Apple apple : inventory) {
if (Color.GREEN.equals(apple.getColor())) {
result.add(apple);
}
}
return result;
}
}
녹색 사과를 선택해서 필터링을 하였지만 소비자가 빨간 사과도 필터링을 하고 싶어졌다.
또는 좀 더 다양한 색으로 필터링하는 등의 변화에 적절하게 대응하려면 다음과 같은 좋은 규칙이 있다.
거의 비슷한 코드가 반복 존재한다면, 그 코드를 추상화한다.
- 두 번째 시도
색을 파라미터화할 수 있도록 메서드에 파라미터를 추가하면 변화하는 요구사항에 좀 더 유연하게 대응하는 코드를 만들 수 있다.
public static List<Apple> filterGreenApples(List<Apple> inventory, Color color) {
List<Apple> result = new ArrayList<>();
for (Apple apple : inventory) {
if (color.equals(apple.getColor())) {
result.add(apple);
}
}
return result;
}
filterGreenApples(inventory, GREEN); // 사용
농부가 다시 나타나선, '색 이외에도 가벼운 사과와 무거운 사과로 구분할 수 있으면 좋겠다'고 새로운 요구사항은 내논다.
public static List<Apple> filterGreenApples(List<Apple> inventory, int weight) {
List<Apple> result = new ArrayList<>();
for (Apple apple : inventory) {
if (apple.getWeight() > weight) {
result.add(apple);
}
}
return result;
}
위 코드를 보면 색 필터링 코드와 대부분 중복된다.
소프트 웨어 공학의 DRY(Dont repeat yourself, 같은 것을 반복하지 말 것) 원칙을 어기는 것.
모든 속성을 파라마티로 넘겨줄 수도 있겠지만 코드가 무엇을 의미하는지 알기 힘들 뿐더러, 앞으로 요구사항이 바뀌었을 때 유연하게 대응할 수 없다.
동적 파라미터화를 이용해 유연성을 얻는 방법을 알아보자.
동적 파라미터화
파라미터를 추가하는 방법이 아닌, 변화하는 요구사항에 좀 더 유연하게 대응할 수 있는 방법이 절실하다.
우리의 선택 조건을 Apple의 어떤 속성에 기초해서 불리언 값을 반환하는 방법이 있다.
참 또는 거짓을 반환하는 함수를 프리디케이트(Predicate)라고 한다.
선택 조건을 결정하는 인터페이스를 정의하자.
public interface ApplePredicate {
boolean test(Apple apple);
}
// 다양한 선택 조건을 대표하는 여러 버전의 ApplePredicate를 정의
public class AppleGreenColorPredicate implements ApplePredicate {
@Override
public boolean test(Apple apple) {
return Color.GREEN.equals(apple.getColor());
}
}
public class AppleHeavyWeightPredicate implements ApplePredicate {
@Override
public boolean test(Apple apple) {
return apple.getWeight() > 150;
}
}
ApplePredicate는 사과 전택 전략을 캡슐화.
이를 전략 디자인 패턴이라고 함.
전략 디자인 패턴은 각 알고리즘(전략)을 캡슐화하는 알고리즘 패밀리를 정의한 다음에 런타임에 알고리즘을 선택하는 기법.
우리 예제에서는 ApplePredicate가 알고리즘 패밀리고, AppleGreenColorPredicate, AppleHeavyWeightPredicate가 전략이다.
동적 파라미터화, 메서드가 다양한 동작(전략)을 받아서 내부적으로 다양한 동작을 수행할 수 있다.
- 세 번째 시도 : 추상적 조건으로 필터링
public static List<Apple> filterGreenApples(List<Apple> inventory, ApplePredicate p) {
List<Apple> result = new ArrayList<>();
for (Apple apple : inventory) {
if (p.test(apple)) {
result.add(apple);
}
}
return result;
}
if문을 프리디케이트 객체로 사과 검사 조건을 캡슐화 했다.
처음 코드에 비해 좀더 유연한 코드를 얻었으며, 동시에 가독성도 좋아지고 사용도 쉬워졌다.
한 개의 파라미터로 다양한 동작을 할 수 있다.
하나의 동작당 하나의 클래스를 구현해서 인스턴스화하는 과정을 개선해보자.
복잡한 과정 간소화
새로운 동작을 전달하려면 ApplePredicate 인터페이스를 구현하는 여러 클래스를 정의한 다음에 인스턴스화 해야한다.
자바는 클래스의 선언과 인스턴스화를 동시에 수행할 수 있도록 익명 클래스라는 기법을 제공한다.
익명 클래스
익명 클래스는 자바의 지역 클래스(local class, 블록 내부에 선언된 클래스)와 비슷한 개념이다.
익명 클래스는 말 그대로 이름이 없는 클래스다.
익명 클래스를 이용하면 클래스 선언과 인스턴스화를 동시에 할 수 있다.
- 네 번째 시도 :익명 클래스 사용
List<Apple> inventory = Arrays.asList(new Apple(80, Color.GREEN), new Apple(80, Color.GREEN), new Apple(80, Color.GREEN));
filterGreenApples(inventory, new ApplePredicate() {
@Override
public boolean test(Apple apple) {
return Color.RED.equals(apple.getColor());
}
});
}
익명 클래스는 여전히 많은 공간을 차지한다.
return Color.RED.equals(apple.getColor()); // 이 코드 한줄 만 있으면 된다.
- 다섯 번째 시도 : 람다 표현식 사용
filterGreenApples(inventory, A -> A.getColor().equals(Color.RED));
매우 간단하고 간결한 코드가 되었다.
- 여섯 번째 시도 : 리스트 형식으로 추상화
public interface Predicate<T> {
boolean test(T t);
}
public static <T> List<T> filterGreenApples(List<T> list, Predicate<T> p) {
List<T> result = new ArrayList<>();
for (T e : list) {
if (p.test(e)) {
result.add(e);
}
}
return result;
}
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
List<Integer> evenNumbers = filterGreenApples(numbers, (Integer i) -> i % 2 == 0);
제네릭을 사용하면 Apple뿐 아니라 바나나, 오렌지, 정수, 문자열 등 리스트에 필터 메서드를 사용할 수 있다.
마치며
- 동적 파라미터화에서는 메서드 내부적으로 다양한 동작을 수행할 수 있도록 코드를 메서드 인수로 전달한다.
- 동적 파라미터화를 이용하면 변화하는 요구사항에 더 잘 대응할 수 있는 코드를 구현할 수 있으며 나중에 엔지니어링 비용을 줄일수 있다.
- 코드 전달 기법을 이용하면 동작을 메서드의 인수로 전달할 수 있다.
'Study > Modern-Java-In-Action' 카테고리의 다른 글
Modern Java Ch04. Stream 소개 (0) | 2023.01.19 |
---|---|
Modern Java Ch03. Lambda (0) | 2023.01.19 |
JVM-Runtime data area (0) | 2023.01.17 |
JVM - Class Loader (0) | 2023.01.17 |
JVM이란 (0) | 2023.01.17 |
댓글