럿고의 개발 노트
동작 파라미터화 본문
동작 파라미터화
동작 파라미터화란?
- 메서드 내부적으로 다양한 동작을 수행할 수 있도록 코드를 메서드 인수로 전달합니다.
- 자주 바뀌는 요구사항에 대해 효과적으로 대응 할 수 있으며, 아직 어떻게 실행할 것인지 결정하지 않은 코드 블럭을 의미합니다.
- 이 코드 블럭은 나중에 프로그램에서 호출되며 코드 블록의 실행은 나중으로 미뤄집니다. 결과적으로 코드 블록에 따라서 메서드의 동작이 파라미터화가 됩니다.
예제
잘못된 코드
사과의 클래스 생성
enum Color {
RED, GREEN
}
public class Apple {
private int weight;
private Color color;
public Apple(int weight, Color color) {
this.weight = weight;
this.color = color;
}
public int getWeight() {
return weight;
}
public Color getColor() {
return color;
}
}
for-each를 이용한 녹색사과, 빨간사과만 필터링 하는 코드
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class FilterApples {
private final static List<Apple> inventory = Arrays.asList(
new Apple(80, Color.RED),
new Apple(155, Color.GREEN),
new Apple(120, Color.GREEN)
);
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> filterRedApples(List<Apple> inventory){
List<Apple> result = new ArrayList<>();
for (Apple apple : inventory){
if(Color.RED.equals(apple.getColor())){
result.add(apple);
}
}
return result;
}
}
- 빨간사과만 필터링하는 예제와 녹색사과만 필터링하는 예제에 중복코드가 굉장히 많아 보입니다.
- 그 코드를 추상화 하는 것을 추천한다.
Color
를 파라미터화 하기 및 무게 필터링 메서드도 생성
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class FilterApples {
private final static List<Apple> inventory = Arrays.asList(
new Apple(80, Color.RED),
new Apple(155, Color.GREEN),
new Apple(120, Color.GREEN)
);
public static List<Apple> filterAppleByColor(List<Apple> inventory, Color color){
List<Apple> result = new ArrayList<>();
for (Apple apple : inventory){
if(apple.getColor().equals(color)){
result.add(apple);
}
}
return result;
}
public static List<Apple> filterAppleByWeight(List<Apple> inventory, int weight){
List<Apple> result = new ArrayList<>();
for (Apple apple : inventory){
if(apple.getWeight() > weight){
result.add(apple);
}
}
return result;
}
public static void main(String[] args) {
List<Apple> greenApples = filterAppleByColor(inventory, Color.GREEN);
List<Apple> redApples = filterAppleByColor(inventory, Color.RED);
List<Apple> heavyApples = filterAppleByWeight(inventory, 150);
}
}
- 위의 코드처럼
Color
를 인수로 받아서 한 메서드로 같이 했다. - 또한 이번에 하면서 무개를 가지고 비교하는 코드도 작성하였는데, 두개의 메서드가 중복코드가 많다 보니
DRY(Don't Repeat Yourself)
의 원칙을 어기게 되었다.
가능한 모든 속성으로 필터링
- 그렇다면 모든 필터링할 속성을 인수로 받으면 되지 않을까라는 생각을 할 수도 있다.
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class FilterApples {
private final static List<Apple> inventory = Arrays.asList(
new Apple(80, Color.RED),
new Apple(155, Color.GREEN),
new Apple(120, Color.GREEN)
);
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;
}
public static void main(String[] args) {
List<Apple> greenApples2 = filterApples(inventory, Color.GREEN, 0, true);
List<Apple> heavyApples2 = filterApples(inventory, null, 150, false);
}
}
- 딱봐도 정신이 없고, 요구사항이 변하게 되었을때도 유연하게 대응할 수도 없게 될 것이다.
- 이런 경우에 동작 파라미터화를 이용하면 훨씬 더 유연하고 깔끔한 코드를 짤수 있을 것이다.
동작 파라미터화를 이용한 코드
Predicate
는 참과 거짓을 반환하는 함수이다. 선택 조건을 결정하는 인터페이스를 생성한다.
public interface ApplePredicate {
boolean test (Apple apple);
}
public class AppleHeavyWeightPredicate implements ApplePredicate {
@Override
public boolean test(Apple apple) {
return apple.getWeight() > 150;
}
}
public class AppleGreenColorPredicate implements ApplePredicate {
@Override
public boolean test(Apple apple) {
return Color.GREEN.equals(apple.getColor());
}
}
- 이런식으로
test
메서드에 들어갈 조건을 각 클래스로 정의해주면 되는데 이런 것을전략
이라고 이야기 한다. - 디자인 패턴에서는
전략 패턴
이라는 것이 있는데, 각 전력들을 캡슐화하는 전략 패밀리를 정의해 둔 다음에 런타임에 전략을 선택하는 기법이다. - 이와 관련된 글은 한번 포스팅을 한 적이 있다. 아래에 링크를 남겨놓겠습니다.
- 전략패턴 & 인터페이스 추상화
- 그러나 위에서 구현한것 처럼 각각의 전략들을 클래스화를 한 다는 것은 번거롭고 시간낭비가 엄청날 것이다.
- 자바는 클래스 선언과 인스턴스화를 동시에 할 수 있는
익명 클래스
를 지원한다.
익명클래스를 사용
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class FilterApples {
private final static List<Apple> inventory = Arrays.asList(
new Apple(80, Color.RED),
new Apple(155, Color.GREEN),
new Apple(120, Color.GREEN)
);
List<Apple> redApples = correctFilterApples(inventory, new ApplePredicate(){
@Override
public boolean test(Apple apple) {
return Color.RED.equals(apple.getColor());
}
});
public static List<Apple> correctFilterApples(List<Apple> inventory, ApplePredicate applePredicate){
List<Apple> result = new ArrayList<>();
for (Apple apple : inventory){
if(applePredicate.test(apple)){
result.add(apple);
}
}
return result;
}
}
- 그러나 익명 클래스는 많은 공간을 차지하게 되어 클래스 내에서 코드가 복잡해질수 있다는 단점을 가지고 있다.
- 이걸 해결하기 위해서 자바8에서는
람다
를 지원한다.
람다표현식 사용
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class FilterApples {
private final static List<Apple> inventory = Arrays.asList(
new Apple(80, Color.RED),
new Apple(155, Color.GREEN),
new Apple(120, Color.GREEN)
);
List<Apple> redApples = correctFilterApples(inventory, (Apple apple) -> Color.RED.equals(apple.getColor()));
public static List<Apple> correctFilterApples(List<Apple> inventory, ApplePredicate applePredicate){
List<Apple> result = new ArrayList<>();
for (Apple apple : inventory){
if(applePredicate.test(apple)){
result.add(apple);
}
}
return result;
}
}
- 이렇게 한다면 코드가 훨씬 간결해진 것이 느껴질 것이다.
정리
- 동작 파라미터화에서는 메서드 내부적으로 다양한 동작을 수행할 수 있도록 코드를 메서드 인수로 전달한다.
- 동작 파라미터화를 이용하면 변화하는 요구사항에 더 잘 대응할 수 있는 코드를 구현할 수 있으며 나중에 엔지니어링 비용을 줄 일 수 있다.
- 코드 전달 기법을 이용한다면 동작을 메서드로의 인수로 전달 할 수 있으나, 클래스들이 많이 지거나
익명클래스
로 구현하게 된다면 코드가 장황해지는 단점이 존재했다. 해결 하기 위해서는람다
를 이용하면 훨씬 더 깔끔한 코드를 작성할 수 있을 것이다.
'Java Note > 모던 자바 인 액션(Modern Java in Action)' 카테고리의 다른 글
자바 8의 새로운 개념과 기능 (0) | 2020.03.07 |
---|
Comments