티스토리 뷰

  • 변화하는 요구사항은 소프트웨어 엔지니어링에서 피할 수 없는 문제.

  • 동작 파라미터화를 이용하면 자주 바뀌는 요구사항에 효과적으로 대응할 수 있음.

  • 동작 파라미터화란 아직은 어떻게 실행할 것인지 결정하지 않은 코드 블록을 의미함. => '어떠한 동작을 해라'는 것을 파라미터로 전달하는 것.

  • 하지만 동작 파라미터화를 추가하려면 쓸데없는 코드가 늘어나는데, 이는 자바 8 람다 표현식으로 해결함.

 

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

2.1.1 첫 번째 시도 : 녹색 사과 필터링

  • 농장의 사과목록에서 녹색 사과만 필터링하는 기능을 추가하는 경우
// 1. 초록 사과를 필터링 하는 메소드
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;
}
  • 만약 빨간 사과도 필터링하고 싶은 경우, 위 메서드를 똑같이 복사해서 if문 조건을 빨간 사과로 바꾸는 방법을 선택할 수 있음. 그러나 이렇게 대응할 경우 이후 좀 더 다양한 색이 추가되고 필터링하는 요구사항에 적절히 대응할 수 없음. 
  • 이런 상황에는 '거의 비슷한 코드가 반복 존재한다면 그 코드를 추상화한다.'는 규칙을 적용해야함.

2.1.2 두 번째 시도 : 색을 파라미터화

  • 색을 파라미터화할 수 있도록 메서드에 파라미터를 추가하여 변화하는 요구사항에 좀 더 유연하게 대응하는 코드를 만들 수 있음.
// 2. 컬러도 파라미터로 받아서 어떤 컬러로 필터링할 것인지 개선
public static List<Apple> filterApplesByColor(List<Apple> inventory, Color color) {
	List<Apple> result = new ArrayList<>();
	for (Apple apple : inventory) {
		if (apple.getColor().equals(color)) {
			result.add(apple);
		}
	}
	return result;
}
  • 만약 색이 아니라 무게로 사과를 구분하고자 하는 경우에는 아래와 같이 코드를 작성할 수 있음. 
// 3. 무게로 사과를 필터링하는 메소드
public static List<Apple> filterApplesByWeight(List<Apple> inventory, int weight) {
	List<Apple> result = new ArrayList<>();
	for (Apple apple : inventory) {
		if (apple.getWeight() > weight) {
			result.add(apple);
		}
	}
	return result;
}

 

2.1.3 세 번째 시도 : 가능한 모든 속성으로 필터링

  • 모든 필터링 조건을 합쳐서 플래그로 구분하여 필터링하도록 코드를 작성함.
// 4. 가능한 모든 속성으로 필터링하도록 수정
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;
}
  • 그러나 위 처럼 개발하는 경우 flag의 역할이 명확하지 않고, 게다가 앞으로 요구사항이 바뀔 경우 유연하게 대응할 수 없음. (사과의 크기, 모양, 출하지 등으로 사과를 필터링하고 싶은 경우)

 

2.2 동작 파라미터화

  • 선택 조건을 결정하는 프레디케이트 인터페이스를 정의하자. 그러면 다양한 선택 조건을 대표하는 여러 버전의 프레디케이트를 정의할 수 있음. 
// 선택 조건을 결정하는 인터페이스로 따로 뺀다
// 전략 디자인 패턴
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());
	}
}
  • filterApples에서 ApplePredicate 객체를 받아 애플의 조건을 검사하도록 메서드를 고치자.
  • 이렇게 동작 파라미터화, 즉 메서드가 다양한 동작 (또는 전략)을 받아서 내부적으로 다양한 동작을 수행할 수 있음.
  • 이렇게 수정하면 filterApples 메서드 내부에서 컬렉션을 반복하는 로직과 컬렉션의 각 요소에 적용할 동작을 분리할 수 있음.

2.2.1 네 번째 시도 : 추상적 조건으로 필터링

// 5. 프레디케이트를 인자로 받음으로써 컬렉션을 반복하는 로직과 컬렉션의 각 요소에 적용할 동작을 분리할 수 있음
public static List<Apple> filterApples(List<Apple> inventory, ApplePredicate p) {
	List<Apple> result = new ArrayList<>();
	for (Apple apple : inventory) {
		if (p.test(apple)) {
			result.add(apple);
		}
	}
	return result;
}
  • 이제 필요한 대로 다양한 ApplePredicate를 만들어서 filterApples 메서드로 전달할 수 있음.
  • 컬렉션 탐색 로직과 각 항목에 적용할 동작을 분리할 수 있다는 것이 동작 파라미터화의 강점임. 따라서 한 메서드가 다른 동작을 수행하도록 재활용할 수 있음.
  • 하지만 여러 클래스를 구현해서 인스턴스화하는 과정이 조금은 거추장스럽게 느껴질 수 있음.

 

2.3 복잡한 과정 간소화

List<Apple> inventory = Arrays.asList(new Apple(80, Color.GREEN), new Apple(155, Color.GREEN), new Apple(120, Color.RED));
// 직접 클래스를 구현해서 인자로 넘김
List<Apple> heavyApples = FilteringApples.filterApples(inventory, new AppleHeavyWeightPredicate());
List<Apple> greenApples = FilteringApples.filterApples(inventory, new AppleGreenColorPredicate());
  • 현재 filterApples 메서드로 새로운 동작을 전달하려면 ApplePredicate 인터페이스를 구현하는 여러 클래스를 정의한 다음에 인스턴스화해야 함. 이는 상당히 번거로운 작업이며 시간 낭비임.
  • 자바는 클래스 선언과 인스턴스화를 동시에 수행할 수 있도록 익명 클래스라는 기법을 제공함. 익명 클래스를 이용하면 코드의 양을 줄일 수 있음.

 

2.3.1 익명 클래스

  • 익명 클래스는 자바의 지역 클래스와 비슷한 개념임. 익명 클래스는 말 그대로 이름이 없는 클래스. 
  • 익명 클래스를 이용하면 클래스 선언과 인스턴스화를 동시에 할 수 있음.

2.3.2 다섯 번째 시도 : 익명 클래스 사용

// 익명 클래스를 이용해서 즉석에서 구현
List<Apple> redApples = FilteringApples.filterApples(inventory, new ApplePredicate() {
	@Override
	public boolean test(Apple apple) {
		return Color.RED.equals(apple.getColor());
	}
});
  • 하지만 익명 클래스로도 부족한 점이 있음. 여전히 많은 공간을 차지하며, 많은 프로그래머가 익명 클래스 사용에 익숙하지 않음.

2.3.3 여섯 번째 시도 : 람다 표현식 사용

// 위 코드를 람다를 이용해서 간결하게 구현할 수 있다
List<Apple> result = FilteringApples.filterApples(inventory, apple -> Color.RED.equals(apple.getColor()));
  • 자바 8의 람다 표현식을 이용해서 위 예제 코드를 다음처럼 간단하게 재구현할 수 있음.

2.3.4 일곱 번째 시도 : 리스트 형식으로 추상화 (제네릭을 이용)

// 제네릭을 이용해서 좀 더 제너럴하게 개선
public interface Predicate<T> {
	boolean test(T t);
}
// 제네릭을 이용해서 좀 더 제너럴하게 수정
public static <T> List<T> filter(List<T> list, Predicate<T> p) {
	List<T> result = new ArrayList<>();
	for (T e : list) {
		if (p.test(e)) {
			result.add(e);
		}
	}
	return result;
}

 

2.4 실전 예제

  • 자바 API의 많은 메서드를 다양한 동작으로 파라미터화 할 수 있음. 

2.4.1 Comparator로 정렬하기

  • 컬렉션 정렬은 반복되는 프로그래밍 작업. 따라서 개발자에게는 변화하는 요구사항에 쉽게 대응할 수 있는 다양한 정렬 동작을 수행할 수 있는 코드가 절실함.
  • 자바 8의 List에는 sort 메서드가 포함되어 있음. sort 동작을 Comparator 객체를 이용해서 파라미터화 할 수 있음.
// Comparator를 sorting할 때 동작파라미터로 전달해서 처리
inventory.sort(new Comparator<Apple>() {
	@Override
	public int compare(Apple o1, Apple o2) {
		return o1.getWeight().compareTo(o2.getWeight());
	}
});

 

2.4.2 Runnable로 코드 블록 실행하기

  • 자바에서는 Runnable 인터페이스를 이용해서 실행할 코드 블록을 지정할 수 있음.
  • Runnable을 이용해서 다양한 동작을 스레드로 실행할 수 있음.
// Thread를 수행할 때 Runnable을 동작파라미터로 전달해서 수행되도록 처리
Thread t1 = new Thread(new Runnable() {
	@Override
	public void run() {
		System.out.println("Hello world");
	}
});

 

2.5 마치며

  • 동작 파라미터화에서는 메서드 내부적으로 다양한 동작을 수행할 수 있도록 코드를 메서드 인수로 전달함.
  • 동작 파라미터화를 이용하면 변화하는 요구사항에 더 잘 대응할 수 있는 코드를 구현할 수 있으며 나중에 엔지니어링 비용을 줄일 수 있음.
  • 코드 전달 기법을 이용하면 동작을 메서드의 인수로 전달할 수 있음. 자바 8 이전에는 코드를 지저분하게 구현해야 했으나, 자바 8에서는 인터페이스를 상속받아 여러 클래스를 구현해야 하는 수고를 없앨 수 있는 방법을 제공함.
  • 자바 API의 많은 메서드는 정렬, 스레드, GUI 처리 등을 포함한 다양한 동작으로 파라미터화 할 수 있음.
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/09   »
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 30
글 보관함