[Modern Java] 동작 파라미터화 코드 전달하기

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

자바 어플리케이션을 개발하면서 요구사항은 항상 변화한다. 이런 변화에 대응하는 방법은 코드를 유연하게 만드는 방법이다. 다음과 같은 코드가 있다고 가정하자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public enum Color {
RED,
GREEN
}

public class Apple {
private Color color;
private double weight;

public Apple(Color color, double weight) {
this.color = color;
this.weight = weight;
}
}

변화하는 요구사항에 대응하기

첫번째 요구사항으로 녹색의 사과만 필터링을 하는 기능이 필요하다고 한다면 다음과 같이 코드를 만들 수 있다.

1
2
3
4
5
6
7
8
9
10
11
private List<Apple> getGreenApples(List<Apple> apples) {
List<Apple> result = new ArrayList<>();

for(Apple apple : apples) {
if(Color.GREEN.equals(apple.getColor())) {
result.add(apple);
}
}

return result;
}

근데 요구사항이 변화해서 다양한 색을 필터링 하는 기능이 필요해졌다고 하자. 그럼 코드는 이렇게 변할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
private List<Apple> getApplesFilteringByColor(List<Apple> apples, Color color) {
List<Apple> result = new ArrayList<>();

for(Apple apple : apples) {
if(color.equals(apple.getColor())) {
result.add(apple);
}
}

return result;
}

여기에 무게를 기준으로 필터링하는 기능이 필요해졌다고 한다면 또 다른 코드를 생산해야 한다.

1
2
3
4
5
6
7
8
9
10
11
private List<Apple> getApplesFilteringByWeight(List<Apple> apples, double weight) {
List<Apple> result = new ArrayList<>();

for(Apple apple : apples) {
if(weight >= apple.getWeight()) {
result.add(apple);
}
}

return result;
}

이런 중복이 싫다면 flag를 통해서 동작의 분기를 적용할 수 있다. 하지만 좋은 코드는 아니다.

1
2
3
4
5
6
7
8
9
10
11
private List<Apple> getFilteredApples(List<Apple> apples, Color color, double weight, boolean flag) {
List<Apple> result = new ArrayList<>();

for(Apple apple : apples) {
if((flag && color.equals(apple.getColor())) || (!flag && weight >= apple.getWeight())) {
result.add(apple);
}
}

return result;
}

동작 파라미터화

이런 다양한 변화에 대응하는 방법중에 하나는 전략패턴을 사용해 동작을 파라미터화 하는 것이다. Predicate 메소드를 통해 동작의 세부사항을 결정하는 방법이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public interface ApplePredicate {
boolean judge(Apple apple);
}

public class AppleColorPredicate implements ApplePredicate {
private Color color;

@Override
public boolean judge(Apple apple) {
return this.color.equals(apple.getColor());
}

public AppleColorPredicate(Color color) {
this.color = color;
}
}

public class AppleUnderWeightPredicate implements ApplePredicate {
private double weight;

@Override
public boolean judge(Apple apple) {
return apple.getWeight() <= this.weight;
}

public AppleUnderWeightPredicate(double weight) {
this.weight = weight;
}
}
1
2
3
4
5
ApplePredicate greenApplePredicate = new AppleColorPredicate(Color.GREEN);
ApplePredicate under150WeightApplePredicate = new AppleUnderWeightPredicate(150);

List<Apple> greenApples = getFilteredApples(apples, greenApplePredicate);
List<Apple> under150WeightApples = getFilteredApples(apples, under150WeightApplePredicate);
1
2
3
4
5
6
7
8
9
10
11
private List<Apple> getFilteredApples(List<Apple> apples, ApplePredicate applePredicate) {
List<Apple> result = new ArrayList<>();

for(Apple apple : apples) {
if(applePredicate.judge(apple)) {
result.add(apple);
}
}

return result;
}

이 방법으로 코드는 유연해지고 전보다 더욱 읽기 쉬워졌다. 하지만 Predicate 클래스를 만들어야 하는 번거로움이 존재한다. 이 부분은 익명 클래스로 대체할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
List<Apple> greenApples = getFilteredApples(apples, new ApplePredicate() {
@Override
public boolean judge(Apple apple) {
return Color.GREEN.equals(apple.getColor());
}
});

List<Apple> under150WeightApples = getFilteredApples(apples, new ApplePredicate() {
@Override
public boolean judge(Apple apple) {
return 150 >= apple.getWeight();
}
});

이를 통해 새로운 클래스를 계속 만들어야하는 번거로움은 사라졌지만 아직도 코드가 길어보인다. 이는 Lambda Expression을 통해 더욱 간소화 할 수 있다.

1
2
3
List<Apple> greenApples = getFilteredApples(apples, apple -> Color.GREEN.equals(apple.getColor()));

List<Apple> under150WeightApples = getFilteredApples(apples, apple -> 50 >= apple.getWeight());

참고

How Inner Classes Work

Author: Song Hayoung
Link: https://songhayoung.github.io/2021/03/27/Languages/Modern%20Java/chapter2/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.