2장: 동작 파라미터화

동작 파라미터화 코드로 중복을 제거하고 가독성을 높이자.

동작 파라미터화

  • 자주 바뀌는 요구사항에 효과적으로 대응할 수 있다.

  • 아직 어떻게 실행할 것이지 결정하지 않은 코드 블록을 의미한다.

  • 코드 블록은 나중에 프로그램에서 호출하므로, 코드 블록의 실행은 나중으로 미뤄진다.

  • 다음과 같이 '어떤 동작'을 하는 메서드를 파라미터로 넘기면 다양한 기능을 수행할 수 있다.

    • 리스트의 모든 요소에 대해서 ‘어떤 동작'을 수행할 수 있음

    • 리스트 관련 작업을 끝낸 다음에 ‘어떤 다른 동작'을 수행할 수 있음

    • 에러가 발생하면 ‘정해진 어떤 다른 동작'을 수행할 수 있음

필요성

  • 자주 바뀌는 조건에 따라 객체들을 필터링하는 메서드가 필요하다는 요구사항이 있다고 생각해보자. 새로운 조건마다 하나씩 필터링 메서드를 구현하면 중복이 많이 생겨 DRY(Don't Repeat Yourself) 원칙을 위배하게 된다.

  • 아래는 여러 필터링 메서드가 생겨 중복되는 부분이 생긴 예제이다.

public static List<Apple> filterApplesByColor(List<Apple> inventory, Color color) {
	List<Apple> result = new ArrayList<>();
	for (Apple appe: inventory) {
		if (apple.getColor().equals(color)) {
			result.add(people);
		}
	}
	return result;
}
public static List<Apple> filterApplesByWeight(List<Apple> inventory, int weight) {
	List<Apple> result = new ArrayList<>();
	for (Apple appe: inventory) {
		if (apple.getWeight() >= weight) {
			result.add(people);
		}
	}
	return result;
}
  • 여러 필터링 메서드를 하나로 합치고 모든 속성을 메서드 파라미터로 추가할 수도 있지만, 쉽게 이해할 수 없는 코드가 되버린다.

public static List<Apple> filterApples(List<Apple> inventory, Color color, int weight, boolean flag**)
	List<Apple> result = new ArrayList<>();
	for (Apple appe: inventory) {
		if (apple.getColor().equals(color)) {
			result.add(people);
		}
	}
	return result;
}

사용하기

클래스

  • 인터페이스를 만들고, 해당 인터페이스를 구현하는 클래스를 조건이 바뀔때마다 생성한다.

  • 이렇게 되면 특정 조건을 사용하고 싶을 때 특정 구현체 클래스를 사용하면 되므로, 전략 디자인 패턴 형태이다.

public interface ApplePredicate {
	boolean test (Apple apple);
}

public class AppleHeavyWeightPredicate implements AppplePredicate {
	public boolean test(Apple apple) {
		return apple.getWeight() > 150;
	}
}

public class AppleColorPredicate implements AppplePredicate {
	public boolean test(Apple apple) {
		return GREEN.equals(apple.getColor());
	}
}
  • 조건을 검사하기 위해 아래와 같이 인터페이스 구현체를 입력 인자로 받고, 내부에서는 해당 구현체의 메서드를 사용하도록 한다.

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;
}
  • 아래와 같이 구현 클래스의 인스턴스를 넘겨주어 동작 파라미터화한다.

List<Apple> redAndHeavyApples = filterApples(inventory, new AppleRedAndHeavyPredicate());

익명 클래스

  • 위에서 클래스를 모두 생성한 후 인스턴스를 넘기는 대신, 익명 클래스로 구현하여 한 번만 사용되는 경우에 효율적으로 동작하도록 한다.

  • 하지만 익명 클래스의 사용은 코드를 장황하게 하며 이해를 어렵게 만들기 때문에 사용하지 않는게 좋다.

List<Apple> redApples = filterApples(inventory, new ApplePredicate() {
	public boolean test(Apple apple) {
		return RED.equals(apple.getColor());
	}
});

람다

  • 익명 클래스 대신 람다를 사용해 간결하게 표현할 수 있다.

List<Apple> result = filterApples(inventoruy, (Apple apple) -> RED.equals(apple.getColor()));

추상화

  • 아래와 같이 추상화하면 Apple 객체 뿐만 아니라 다양한 객체의 리스트를 필터링하여 반환할 수 있다.

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;
}

동작 파라미터화 적용 사례

Comparator

  • 자바에서 제공하는 Comparator 인터페이스를 구현하여, 정렬을 위한 메서드 sort()의 입력 인자로 넣을 수 있다.

public interface Comparator<T> {
	int compare(T o1, T o2);
}
inventory.sort((Apple a1, Apple a2) -> a1.getWeight().compareTo)a2.getWeight()));

Runnable

  • 스레드가 어떤 코드를 실행할 지 알 수 있도록 동작 파라미터화하여 전달한다.

Thread t = new Thread(() -> System.out.println("Hello world"));

Callable

  • ExecutorService 인터페이스는 태스크 제출과 실행 과정의 연관성을 끊어준다.

  • ExecutorService를 이용하면 태스크를 스레드 풀로 보내고 결과를 Future로 저장할 수 있다는 점이 스레드와 Runnable을 이용하는 방식과는 다르다.

  • 아래와 같이 태스크를 정의해 ExecutorService로 보내면, 해당 태스크가 실행될 때 실행한 스레드의 이름을 반환하게 된다.

ExecutorService executorService = Excutors.newCachedThreadPool();

Future<String> threadName = executorService.submit(new Callable<String>() {
	@Override
	public String call() throws Exception {
		return Thread.currentThread().getName();
	}
});

자바 FX의 GUI 동작

  • 마우스 클릭, 문자열 위치 이동 등의 동작에 반응하는 메서드가 있다.

  • 이 메서드들에게 이벤트 발생 시 어떤 동작을 할 지를 EventHandler 형태로 동작 파라미터화하여 전달한다.

Button button = new Button("Send");
button.setOnAction(new EventHandler<ActionEvent>() {
	public void handle(ActionEvent event) {
		lable.setText("Sent!!");
	}
});

Last updated