🧑🌾 사과를 선별해주세요! 색깔, 무게, 원산지로요!
🧑💻 네! ( 그렇다면 사과의 색깔, 무게, 원산지를 입력값으로 받는 메서드를 구현해야겠다.!)🧑🌾 앗! 오늘은 색깔, 무게로만 선별하고 싶어요!
🧑💻 앗! 네! ( 그렇다면 사과의 색깔, 무게만 입력값으로 받는 메서드를 구현해야겠다.!)🧑🌾 앗! 색깔, 무게, 원산지 그리고 하여튼..!!! 이거 다 해주세요 ! 또 요청할게요!! !! !!
🧑💻 아이고...
아래처럼 변경이 큰 상황에서는 어떻게 해야 유연한 설계를 할 수 있을까?
여러 코드가 중복될 것이다. 색깔로 선별하는 메서드, 색깔과 무게로 선별하는 메서드는 색깔로 선별하는 코드가 중복되게 될 것이다.
이렇데 단순히 복사-붙여넣기
로 만든 코드는 다음과 같은 문제점을 초래할 수 있다.
결국 모든 상황과 변경에 대한 메서드를 만들어 놓는 것은 좋은 방법은 아니다.
위 상황은 선별하는 값이 중요한 것이 아니라 어떤 동작으로 선별할 것인지가 중요하다. 즉, 선별하는 동작의 정보를 메서드의 입력값으로 전달해야한다. 그리고 이것이 동작을 파라미터화 하는 방법이다.
동작을 메서드의 입력 값으로 전달할 수 있을까?
자바의 인터페이스를 활용하면 가능하다. 직접적으로 함수를 전달받을 수는 없지만, 동작을 미리 정해놓은 인터페이스를 이용해서 이를 구현할 수 있다.
즉, (1) 사과를 선별하는 추상메서드를 가진 인터페이스를 만들고, (2) 사과를 선별하는 메서드의 입력값으로 이 인터페이스를 받게 만든다.
그리고 이후에 (3) 구체적인 인터페이스의 구현체를 입력값으로 전달해, 유연한 설계를 만들 수 있다.
interface ApplePredicate {
boolean test(Apple apple); // 사과를 선별하는 추상메서드
}
public class AppleFilter {
public static List<Apple> filterApples(List<Apple> inventory, ApplePredicate predicate) {
List<Apple> result = new ArrayList<>();
for (Apple apple : inventory) {
if (predicate.test(apple)) {
result.add(apple);
}
}
return result;
}
}
// 무거운 사과 선별
class HeavyApplePredicate implements ApplePredicate {
public boolean test(Apple apple) {
return apple.getWeight() > 150;
}
}
// 녹색 사과 선별
class GreenApplePredicate implements ApplePredicate {
public boolean test(Apple apple) {
return GREEN.equals(apple.getColor());
}
}
// 무거운 사과 선별
List<Apple> heavyApples =
AppleFilter.filterApples(inventory, new HeavyApplePredicate());
// 녹색 사과 선별
List<Apple> greenApples =
filter.filterApples(inventory, new GreenApplePredicate());
이런 구현방식을 표준화한 패턴을 전략 패턴이라고도 한다.
위의 구현 방식을 통해 유연한 설계를 할 수 있다. 그러나 인터페이스, 구현체등 구현할 양이 많다. 조오오금 복잡하다! 그리고 사실 귀찮다!
이를 개선하기 위해서는 익명 클래스를 도입해볼 수 있다. 그리고 더 간단한 방법은 람다 표현식이다.
위에서 귀찮았던 점은, 클래스를 선언하고 인스턴스화하는 두번의 과정이었다.
익명 클래스는 클래스 선언과 인스턴스화를 한번에 한다.
(1)번과 (2)번의 과정은 동일하기 때문에 생략했다.
List<Apple> heavyApples =
AppleFilter.filterApples(inventory, new ApplePredicate() {
public boolean test(Apple apple) {
return apple.getWeight() > 150;
}
});
그러나... 익명 클래스보다는 더 간단한 람다 표현식이 있다.
List<Apple> heavyApples = AppleFilter.filterApples(inventory,
apple -> apple.getWeight() > 150);
인터페이스 구현 - 구현체 구현 - 사용의 복잡함 보다는 람다 표현식이 조금 더 간결하다.
동작을 전달하기 전에는, 직접 메서드를 구현해야하는 복잡성이 있었다.
그러나 하나의 파라미터(익명클래스)로 다양한 동작을 다룰 수 있다는 강점이 생겼다.
이제 위의 상황에서도 웃으면서 대답할 수 있을 것이다.
🧑🌾 사과를 ~~~로 선별해주세요!
🧑💻 네!
다만 늘 람다 표현식이 좋은 것이 아니라는 것을 알고 넘어가자!
복잡한 로직이나 자주 재 사용되는 동작이라면 클래스로 구현하는게 나을 수 있다.
람다식이 너무 길어지면 오히려 가독성이 떨어질 수 있고, 람다 표현식으로 구현한 동작은 일회성으로 사용되기 때문이다.
개발에서는 정답보다는 선택이 있다는 것을 기억하자!