-
자바 8 - 함수형 인터페이스 Consumer<T>Java, Kotlin, Spring 2022. 1. 12. 08:12
함수형 인터페이스들은
java.util.function패키지에 정의되어 있다. 제공하는 인터페이스의 종류는 공식문서에서 확인하자.이번 포스팅에서는
Consumer<T>에 대하여 알아보자.
Consumer<T>Consumer는 함수형 인터페이스이다.함수형 인터페이스에 대한 설명은 자바의 람다 표현식 - 정의와 사용방법 에서 자세히 다루었다.
@FunctionalInterface public interface Consumer<T>Consumer의 이름을 보면, 무언가 소비하는 역할을 수행할 것이라고 예상할 수 있다.다른 함수형 인터페이스들이 각자의 역할이 비교적 명확히 정해져있는 것에 비해
Consumer는 수행할 역할에 대해서 규정하지 않는다.공식 문서의 정의를 살펴보자.
Consumer는 하나의 입력 인자를 받고 아무 결과도 반환하지 않는 동작을 표현한다.
다른 대부분의 함수형 인터페이스들과 달리,Consumer는 부작용(side-effects)을 일으켜 동작하는 것을 기대한다.Consumer는 Generic 으로 전달되는T타입을 인자로 전달받는 함수를 실행하고, 그 함수는void를 반환한다.해당 함수 내에서 어떠한 역할을 수행하던지 관여하지 않는다.
@FunctionalInterface public interface Consumer<T> { void accept(T t); default Consumer<T> andThen(Consumer<? super T> after) { Objects.requireNonNull(after); return (T t) -> { accept(t); after.accept(t); }; } }Consumer의accept함수를 실행하여 원하는 동작을 구현할 수 있다.
예제
예제를 통하여
Consumer를 직접 사용해보자.예제는 자바 8 함수형 인터페이스 - Predicate 에서 사용했던
Vehicle클래스를 그대로 사용하겠다.사용된 코드는 Github 에서 확인할 수 있다.
다음과 같은 요구조건을 구현해보자.
Vehicle의 상세정보를 확인할 수 있다.- 필요에 따라
Vehicle의 상세 정보를 확인 한 후 기타 정보를 추가적으로 확인할 수 있다.
일부러 2번의 요구사항을 추가해보았다. 핵심은 정해진 상세정보 외에 추가적인 행동을 취할 가능성이 열려있다는 것이다.
VehicleInformation- 차량 정보 제공 클래스먼저
Vehicle의 상세 정보를 확인하는 역할을 수행하는 클래스를 만들어보자.public class VehicleInformation { private Vehicle vehicle; public VehicleInformation(Vehicle vehicle) { this.vehicle = vehicle; } public void print() { System.out.println(vehicle.getType()); System.out.println(vehicle.getVendor()); System.out.println(vehicle.getModelName()); System.out.println(vehicle.getColor()); } }VehicleInformation은Vehicle인스턴스를 가지고,print메소드를 통해vehicle인스턴스의 상세정보를 출력한다.1번 요구사항은 손쉽게 구현하였다. 이제 2번 요구사항을 구현해야 하는데,
VehicleInformation을 변경하지 않으면서 새로운 요구사항에 어떻게 대응할 수 있을까?Open Closed Principle 에 따르면, 소프트웨어는 확장에 개방되어야 하고 수정에는 패쇄되어야 한다. 즉, 새로운 요구사항이 생겨도
VehicleInformation은 수정되지 않는 것이 바람직한 설계방향이다.// ... public void print(Consumer<Vehicle> vehicleConsumer) { print(); vehicleConsumer.accept(vehicle); } }Consumer<Vehicle>타입의 인스턴스를 인자로 받는print메소드를 하나 더 추가했다. 이 함수는print()메소드를 호출한 후에 인자로 받은vehicleConsumer의accept메소드를 실행한다.이렇게 구현해두면
VehicleInformation은 추가될 요구사항에도 전혀 손대지 않아도 된다. 추가되는 요구사항은Consumer<Vehicle>타입으로 구현한 구현체에서 수행하면 되기 때문에VehicleInformation자신이 아닌 외부로 그 책임을 넘긴다.차량 정보 사용 부분 예제
구현한
VehicleInformation을 사용해보자.public class VehicleConsumerService { private Vehicle vehicle; private VehicleInformation vehicleInformation; public VehicleConsumerService() { vehicle = new Vehicle(Vehicle.Type.SUV, "BMW", "M3", "white"); vehicleInformation = new VehicleInformation(vehicle); } public void consume() { vehicleInformation.print((vehicle) -> { System.out.println("Vehicle : " + vehicle); }); } }간단한 예제의 구현을 위해서 인자가 없는 생성자를 구현하고, 그 안에서
vehicle과vehicleInformation을 생성했다.consume()메소드에서vehicleInformation의print()메소드를 실행하게 되는데, 이print메소드는 총 2개의 방식으로 구현해두었다.public void print() { System.out.println(vehicle.getType()); System.out.println(vehicle.getVendor()); System.out.println(vehicle.getModelName()); System.out.println(vehicle.getColor()); } public void print(Consumer<Vehicle> vehicleConsumer) { print(); vehicleConsumer.accept(vehicle); }위 2가지 방식 모두를 사용할 수 있지만, 상세정보 출력 외에도 추가적인 기능을 구현하고자
Consumer<Vehicle>타입을 인자로 받는print메소드를 실행하였다.vehicleInformation.print((vehicle) -> { System.out.println("Vehicle : " + vehicle); });lambda 메소드는 이렇게 inline code 로 직접 구현할 수도 있지만, 별도의 클래스로 정의할 수도 있다.
Consumer<Vehicle>인터페이스를 구현한 람다 코드를 리턴하는 형태의 함수를 구현해보자.public class VehicleConsumer { public Consumer<Vehicle> consume() { return (vehicle) -> { System.out.println("Vehicle : " + vehicle); }; } }그 이후, 사용 부분에서
VehicleConsumer클래스를 인스턴스화하여 사용하면 된다.vehicleInformation.print(new VehicleConsumer().consume());
마무리
Consumer<T>는 인터페이스의 역할을 규정하지 않기 때문에, 오히려 그 사용에 더 주의해야 한다고 생각한다.정의가 말해주듯
Consumer<T>인터페이스의 사용은 부작용(side-effects)를 만들게 되는데, 프로그래밍에서 부작용은 꽤 자주 여러 문제들을 만들어낸다. 말 그대로, 부작용이 생길 수 있다는 점이다.함수의 역할을 인터페이스의 정의에서 예측하기 어려운 점을 생각해보았을 때, 프로그래머가
Consumer<T>인터페이스를 사용할 때엔, 그 역할을 함수의 이름으로 충실히 표현해야 할 것이다.이러한 점에서 생각해보았을 때, 예제에서 사용한
VehicleConsumer와 같은 이름은 안티패턴인 것 같다. 코드를 직접 보지 않으면 그 안에서 무슨 일을 하고 있을 지 가늠할 수 없기 때문이다.실제 업무에서 사용할 때에는 보다 구체적인 이름을 지어서 사용하자.
반응형'Java, Kotlin, Spring' 카테고리의 다른 글
스프링 배치 완벽가이드 2/e - 2장 스프링 배치 (0) 2022.01.12 스프링 배치 완벽가이드 2/e - 1장 배치와 스프링 (0) 2022.01.12 자바 Iterable, Iterator, Collection, List 분석하기 (0) 2022.01.12 자바 8 - 함수형 인터페이스 Predicate<T> (0) 2022.01.12 자바의 람다 표현식 2 - 활용 방법 (0) 2022.01.12