동작 파라미터화란?
우리가 어떻게 코드를 작성하여 일을 하든 소비자 요구사항은 항상 바뀝니다. 이렇게 시시각각으로 변하는 사용자 요구 사항에 대응하려면 어떻게 해야할까요? 우리의 엔지니어링적인 비용이 가장 최소화될 수 있으면 좋을 것입니다. 그뿐 아니라 새로 추가한 기능은 쉽게 구현할 수 있어야 하며 장기적인 관점에서 유지보수가 쉬워야 한다. 이 때 동작 파라미터화를 이용하면 자주 바뀌는 요구사항에 효과적으로 대응할 수 있습니다. 동작 파라미터화란 아직은 어떻게 실행할 것인지 결정하지 않은 코드 블록을 의미합니다. 코드를 보며 더 쉽게 이해해보도록 하겠습니다.
변화하는 요구사항에 대응하기
기존의 농장 재고목록 애플리케이션에 리스트에서 녹색 사과만 필터링하는 기능을 추가한다고 가정해보겠습니다.
- 첫 번째 시도 : 녹색 사과 필터링 🍏
다음은 첫 번째 시도 결과 코드입니다.
이 때 갑자기 농부가 변심하여 녹색 사과 말고 빨간 사과도 필터링 하고 싶어졌다면 어떻게 고쳐야 할까요? 물론 새로운 메서드르르 만들고 if 문을 수정할 수 있지만 나중에 좀 더 다양한 색으로 필터링하는 등의 변화에는 적절하게 대응할 수 없으므로 거의 비슷한 코드가 반복적으로 존재한다면 그 코드를 추상화하는 규칙을 지켜서 고쳐야합니다
- 두 번째 시도 : 색을 파라미터화 📍
색을 파라미터화할 수 있도록 메서드에 파라미터를 추가하면 변화하는 요구사항에 좀 더 유연하게 대응하는 코드를 만들 수 있습니다.
위 코드도 좋은 해결책이라 할 수 있지만 자세히 보면 목록을 검색하고, 각 사과에 필터링 조건을 적용하는 부분의 코드가 색 필터링 코드와 대부분 중복됩니다. 탐색 과정을 고쳐서 성능을 개선하려면 한 줄이 아니라 메서드 전체 구현을 고쳐야 할 것입니다..
색과 무게를 filter라는 메서드로 합치는 방법도 있습니다. 그럼 어떤 기준으로 사과를 필터링 할지 구분하는 또 다른 방법이 필요한데 색이나 무게 중 어떤 것을 기준으로 필터링 할지 가리키는 플래그를 추가할 수 있습니다. (하지만 실무에서는 절대 사용하지 말아야 합니다..)
- 세 번째 시도 : 가능한 모든 속성으로 필터링 ⚙️
참 형편없는 코드입니다.. 앞으로 요구사항이 바뀌었을때 유연하게 대응할 수도 없습니다. 위 코드는 요구 사항이 바뀌면 여러 중복된 필터 메서드를 만들거나 아니면 모든 것을 처리하는 거대한 하나의 필터 메서드를 구현해야 합니다. 다음 절에서 동작 파라미터화를 이용해서 유연성을 얻는 방법을 설명합니다.
동작 파라미터화
사과의 어떤 속성에 기초해서 불리언 값을 반환하는 방법이 있습니다. 참 또는 거짓을 반환하는 함수를 프레디케이트라고 하는데 선택 조건을 결정하는 인터페이스를 정의 해봅시다.
위 조건에 따라서 filter 메서드가 다르게 동작할 것이라고 예상할 수 있습니다. 이를 전략 디자인 패턴이라고 부릅니다.
그런데 ApplePredicate는 어떻게 다양한 동작을 수행할 수 있을까요? filterApples에서 ApplePredicate 객체를 받아 애플의 조건을 검사하도록 메서드를 고쳐야 합니다. 이렇게 동작 파라미터화, 즉 메서드가 다양한 동작이나 전략을 받아서 내부적으로 다양한 동작을 수행할 수 있습니다.
이제 filterApples 메서드가 ApplePredicate 객체를 인수로 받도록 고쳐보겠습니다. 이렇게 하면 filterApples 메서드 내부에서 컬렉션을 반복하는 로직과 컬렉션의 각 요소에 적용할 동작을 분리할 수 있다는 점에서 큰 이득을 얻게됩니다.
- 네 번째 시도 : 추상적 조건으로 필터링 🥊
코드/동작 전달하기
자 이제 첫 번째 코드에 비해 더 유연한 코드를 얻었으며 동시에 가독성도 좋아졌을 뿐 아니라 사용하기도 쉬워졌습니다. 이제 필요한 대로 다양한 ApplePredicate를 만들어서 filterApples 메서드로 전달할 수 있습니다. 유연성을 마음껏 누리세요!
그림에서 보여주는 것처럼 위 예제에서 가장 중요한 구현은 test 메서드입니다. filterApples 메서드의 새로운 동작을 정의하는 것이 test 메서드입니다. 안타깝게도 메서드는 객체만 인수로 받으므로 test 메서드를 ApplePredicate 객체로 감싸서 전달해야 합니다. 다음 절에서는 람다를 이용해서 여러 개의 ApplePredicate 클래스를 정의하지 않고도 “red”.equals(apple.getColor()) && apple.getWeight() > 150 같은 표현식을 filterApples 메서드로 전달하는 방법을 알아보겠습니다.
한 개의 파라미터, 다양한 동작 🎯
동작 파라미터화의 강점은 컬렉션 탐색 로직과 각 항목에 적용할 동작을 분리할 수 있다는 것이 동작 파라미터화의 강점입니다. 따라서 그림에서 보여주는 것처럼 한 메서드가 다른 동작을 수행하도록 재활용할 수 있습니다.
복잡한 과정 간소화
아래 코드에서 요약하는 것처럼 현재 filterApples 메서드로 새로운 동작을 전달하려면 ApplePredicate 인터페이스를 구현하는 여러 클래스를 정의한 다음에 인스턴스화해야 한다, 이는 상당히 번거로운 작업이며 시간 낭비다.
로직과 관련 없는 코드가 많이 추가되었습니다. java는 클래스의 선언과 인스턴스화를 동시에 수행할 수 있도록 익명 클래스라는 기법을 제공합니다. 익명 클래스를 사용하면 코드의 양을 줄일 수 있습니다.
익명 클래스
익명 클래스는 자바의 지역 클래스와 비슷한 개념입니다. 익명 클래스는 말 그대로 이름이 없는 클래스입니다. 익명 클래스를 이용하면 클래스 선언과 인스턴스화를 동시에 할 수 있습니다.
- 다섯 번째 시도 : 익명 클래스 사용 👺
코드의 장황함은 나쁜 특성입니다. 장황한 코드는 구현하고 유지보수하는 데 시간이 오래 걸릴 뿐 아니라 읽는 즐거움을 빼앗는 요소로 개발자로부터 외면받습니다.
지금까지 살펴본 것처럼 동작 파라미터화를 이용하면 요구사항 변화에 더 유연하게 대응할 수 있으므로 모든 프로그래머가 동작 파라미터화를 사용하도록 권장합니다. 우선 람다 표현식을 이용해서 어떻게 코드를 간결하게 정리할 수 있는지 간단히 살펴보겠습니다.
- 여섯 번째 시도 : 람다 표현식 사용 ✨
지금까지 살펴본 내용을 한눈에 요약한 그림입니다.
- 일곱 번째 시도 : 리스트 형식으로 추상화 ⚡️
이제 바나나, 오렌지, 정수, 문자열 등의 리스트에 필터 메서드를 사용할 수 있습니다. 이렇게 해서 유연성과 간결함이라는 두 마리 토끼를 모두 잡았습니다. 🐰
실전 예제
이 절에서는 코드 전달 개념을 더욱 확실히 익힐 수 있도록 Comparator로 정렬하기, Runnable로 코드 블록 실행하기 예제를 소개해봅니다.
Comparator로 정렬하기
컬렉션 정렬은 반복되는 프로그래밍 작업입니다. 개발자에게는 변화하는 요구사항에 쉽게 대응할 수 있는 다양한 정렬 동작을 수행할 수 있는 코드가 필요할 텐데 sort 메서드를 이용해서 알아보겠습니다.
12 번째 라인은 람다 표현식을 이용하여 코드를 간단하게 구현한 모습입니다. 이 처럼 Comparator를 구현해서 sort 메서드의 동작을 다양화 할 수 있습니다.
Runnable로 코드 블록 실행하기
자바 스레드를 이용하면 병렬로 코드 블록을 실행할 수 있습니다.이 때 어떤 코드를 실행할 것인지를 스레드에게 알려주는 방법이 필요한데 자바 8까지는 Thread 생성자에 객체만을 전달할 수 있었으므로 보통 결과를 반환하지 않는 void run 메소드를 포함하는 익명 클래스가 Runnable 인터페이스를 구현하도록 하는 것이 일반적인 방법이었습니다.
자바에서는 Runnable 인터페이스를 이용해서 실행할 코드 블록을 지정할 수 있습니다. 아래 코드를 보겠습니다.
12 번째 라인은 자바 8부터 지원하는 람다 표현식을 이용하여 코드를 구현한 모습입니다. 다음 3장에서는 람다 표현식에 대해 더 자세히 배워보겠습니다. 감사합니다!
참고 자료
Modern Java in Action — 라울 게이브리얼 우르마, 마리오 푸스코, 앨런 마이크로소포트 지음