
어떻게 실행할 것인지 결정하지 않은 코드 블록으로, 나중에 호출한다.
변화하는 요구사항은 소프트웨어 엔지니어링에서 피할 수 없는 문제다. 이는 엔지니어링 비용과 연관되어 있으며, 동작 파라미터화를 통해 비용을 줄일 수 있다.
농부가 초록 사과만 거르는 것을 요청했다.
enum Color { RED, GREEN }
public static List<Apple> fillterGreenApples(List<Apple> inventory){
List<Apple> result = new ArrayList<>(); //농부가 원하는 사과 누적 리스트
for(Apple apple : inventory){
if(GREEN.equals(apple.getColor()){ //녹색 사과 거르기
result.add(apple);
}
}
return result;
}
다음날 아침, 빨간 사과를 거르고 싶다며 요청사항을 변경했다.
fillterGreenApples 메서드를 새로 만들고, 조건문을 바꾸는 방법이 있다.
하지만, 이런 방식을 지양한다면, 나중에 더 다양한 색을 필터링하고자 할 때 적적하게 대응할 수 없다.
이런 상황을 위한 다음의 규칙이 있다.
비슷한 코드가 존재한다면 그 코드를 추상화한다.
색을 파라미터화하면 fillterGreenApples의 코드를 반복하지 않고, 구현할 수 있다.
enum Color { RED, GREEN }
public static List<Apple> fillterGreenApples(List<Apple> inventory, Color color){
List<Apple> result = new ArrayList<>();
for(Apple apple : inventory){
if(apple.getColor().equals(color)){
result.add(apple);
}
}
return result;
}
다음날 색 뿐만 아니라 무게가 150그램 이상인 사과도 거르고 싶다고 요구했다.
이처럼 다양한 조건을 추가할 수 있으므로 다음과 같이 모든 속성 메서드를 파라미터로 추가하여 해결할 수 있다.
enum Color { RED, GREEN }
public static List<Apple> fillterGreenApples(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;
}
...
그러나 다음과 같이 호출하게 되면 True/False는 무엇을 의미하는 것인지 알 수 없어 추후 요구사항에 유연한 대처를 할 수 없다.
List<Apple> greenApples = fillterGreenApples(inventory, GREEN, 0, true);
List<Apple> heavyApples = fillterGreenApples(inventory, null, 150, false);
동작 파라미터화를 이용해 유연성을 얻어 해결한다.
사과의 어떤 속성에 기초하여 boolean값을 반환하면 된다.
이때, True/False를 반환하는 함수를 프레디케이트라고 한다.
선택 조건을 결정하는 인터페이스를 다음과 같이 정의할 수 있다.
public interface ApplePredicate{
boolean test (Apple apple);
}
ApplePredicate를 다음과 같이 여러 버전으로 정의하여 사용할 수 있다.
//무게 150 초과만
public class AppleHeavyWeightPredicate implements ApplePredicate{
public boolean test(Apple apple){
return apple.getWeight() > 150;
}
}
//녹색 사과만
public class AppleGreenColorPredicate implements ApplePredicate{
public boolean test(Apple apple){
return GREEN.equals(apple.getColor());
}
}
📌 전략 디자인 패턴
각 알고리즘을 캡슐화하는 알고리즘 패밀리를 정의한 후, 런타임에 알고리즘(전략)을 선택하는 기법
- 패밀리 : ApplePredicate
- 전략 : AppleHeavyWeightPredicate, AppleGreenColorPredicate
위와 같은 개념을 활용해 다음과 같이 사용할 수 있다.
enum Color { RED, GREEN }
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;
}
다음과 같이 전달하는 객체에 따라 filterApples 메서드의 동작이 결정된다.
//녹색 사과만
public class AppleGreenColorPredicate implements ApplePredicate{
public boolean test(Apple apple){
return GREEN.equals(apple.getColor()) && apple.getWeight() > 150;
}
}
List<Apple> greenAndHeavyApples = filterApples(inventory,new AppleGreenColorPredicate());
한 개의 파라미터, 다양한 동작
컬랙션 탐색 로직과 각 항목에 적용할 동작을 분리할 수 있다는 것이 동작 파라미터화의 강점이다.
익명 클래스를 사용하면 앞서 작성한 코드에서 불필요한 요소들을 제거하여 더욱 가독성 좋게 작성할 수 있다.
익명 클래스
이름이 없는 클래스로 클래스 선언과 인스턴스화를 동시에 할 수 있다. (필요한 구현을 즉석에서 만들어 사용할 수 있음)
List<Apple> greenApples = filterApples(inventory,new ApplePredicate(){
public boolean test(Apple apple){
return GREEN.equals(apple.getColor());
}
});
자바8의 람다 표현식을 사용하면 더욱 간단하고, 가독성 좋게 작성할 수 있다.
List<Apple> result = filterApples(inventory, (Apple apple) -> GREEN.equals(apple.getColor()));