동작 파라미터화 코드 전달하기
자바 어플리케이션을 개발하면서 요구사항은 항상 변화한다. 이런 변화에 대응하는 방법은 코드를 유연하게 만드는 방법이다. 다음과 같은 코드가 있다고 가정하자.
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