Right now do !

[JAVA] 함수적 인터페이스의 활용 - Consumer편

by 지금당장해

이 주제가 JAVA8 부터 지원되는 Lambda식의 일부긴 하지만 아니 그런것으로 알고 있지만 오늘 필자가 이 주제를 논하려는 이유는 이전에 JAVA에서는 없었던 이 개념이 왠지 C#의 Delegate와 매칭되어 맴돌고 있기에 이 관점에서 보려 한다. 

 

필자의 머릿속에 정리된 생각은 이렇다. (아직도 C#이 영혼은 개발 친구이기에 자꾸 그 친구하고 비교 하고있는점 이해바란다.) 함수의 유형에 따라 (인자가 1개인지 2개인지 반환 값이 있는지 등) Consumer, Supplier, Function, Operator, Predicate을 선택하여 함수를 Wrapping하는 또는 대리하는 그런 존재로 이해 한다. 물론 이부분이 더럽게 마음에 안든다. C#의 Deleage는 함수의 ProtoType만 맞다면 누구든 호출해주는데 이것들의 함수 유형을 다 알고 있어야 한다니 ... 사전 지식이 없는 독자라면 뭐 겨우 5가지 가지고 라고 생각 할 수도 있겠지만 Consumer, Supplier, Function, Operator, Predicate가 다가아니고 각각에서 파생된 세부 인터페이스들이 또 몇 개씩 되기 때문이다. 

 

자세한 내용은 아래 URL을 참고하기 바란다. 어느 블로그 이웃님이 잘 정리해 주셨다.

https://palpit.tistory.com/673

 

[Java] 람다식 - 표준 API의 함수적 인터페이스1

람다식은 여러 절로 구성되어 있습니다. Intro. 람다식 기본 문법 & 타겟 타입과 함수적 인터페이스 클래스 멤버와 로컬 변수 사용 표준 API의 함수적 인터페이스1 표준 API의 함수적 인터페이스2 메소드참조 표준..

palpit.tistory.com

 

그럼 어디에 쓸것인가? 본편에서는 이 함수적 인터페이스를 처리에 주입 관점에서 지켜 보려한다. 생산자-소비자 패턴으로 작성한 프로그램의 일부로 예를 들겠다.

 

생산자는 총 4가지의 이벤트를 생성해 내고 그를 소비하는 이벤트 Listener 구현체 입장에서는 이를 분기하여 다른 처리를 해야한다. 다음 코드를 통해 이해 해보자.(생산자의 이벤트를 생성시키는 코드는 생략하였다.)

    @Override
    public void onAlteredMembership(MembershipEvent event) {

		// ①
        Consumer<MembershipEvent> consumer = null;
        switch (event.getValueObject().getEventKind()) {
            case Join:
            	// ②
                consumer = this::onJoinMemberEvent;
                break;
            case Leave:
                consumer = this::onLeaveMemberEvent;
                break;
            case Disappear:
                consumer = this::onDisappearMemberEvent;
                break;
            case Reappear:
                consumer = this::onReappearMemberEvent;
                break;
                default:
                    break;

        };

        if ( consumer != null ) {
        	// ③
            consumer.accept(event);
        }

    }

	// ④
    private void onJoinMemberEvent(MembershipEvent event) {
    // 처리1
    }

    private void onLeaveMemberEvent(MembershipEvent event) {
    // 처리2
    }

    private void onDisappearMemberEvent(MembershipEvent event) {
    // 처리3
    }

    private void onReappearMemberEvent(MembershipEvent event) {
    // 처리4
    }

먼저 onAlteredMemebership함수가 포함된 Class는 회원관리를 하는 Class로 부터 회원의 변동이 있을때 이벤트를 받을 수 있는 소위 Listener Class이다. 즉, 회원 관리를하는 Class에서 회원의 변동이 있을때 이 함수를 통해 알려주는 패턴이다. 생산자 Class에서는 MembershipEvent를 넘겨준다. 이 객체에는 해당 이벤트가 어떤 성격의 데이터를 담고 있는지 알수 있는 열거형 데이터를 담고있다. 소비자 입장에서는 해당 이벤트에 따른 분기 처리가 필요하다. 분기문이나 스위치 문을 통해 각각의 함수를 호출해도 그만이지만 Consumer<T>를 적용해보았다.

주석 ①아래에 코드는 단어 뜻 그대로 소비자형 인터페이스를 선언하는 과정이다. 이때 가변인자로 지정된 타입은 실제 함수으 유일한 인자의 타입과 일치해야 한다. (주석 ④항 참조) 주석 ②항은 스위치 분기에 따라 호출될 함수를 호출 대리자인 consumer변수에 할당하는 과정이다. 일반적인 함수 호출 방식으로 구현한다면 여기서 실제 함수의 호출 Statement가 실행되겠지만 이는 그저 호출 대리자에게 호출 해야할 함수를 알려주는것 뿐이다. 예제에서는 호출 대상을 같은 객체 내의 함수로 했지만 다른 Class에 정의되어 있는 인스턴스 함수 또는 Static함수의 호출도 가능하다. 이런 호출이 필요할 때는 인스턴스 변수명::함수이름, Class명::함수이름 형태로 사용하면 된다.

 

실제 호출은 주석 ③아래의 accept함의 호출을 통해 일어난다. 이때 호출에 사용될 인자를 지정해준다.  그럼 스위치문에서 지정된 함수가 호출된다.

 

여기까지가 다라면 Switch - case에서 바로 호출하는 방식을 취해도 딱히 비 효율적이거나 가독성이 떨어지지 않을 것이다. 하지만 Consumer는 accept(단독실행) 외에 Consumer<T> andThen(Consumer<? super T> after)이라는 인터페이스 함수를 제공하여 호출할 함수들을 굴비처럼 엮어낼수가 있다. 이해를 돕기위해 간단한 코드를 첨한다.

Consumer<MembershipEvent> c1 = this::onJoinMemberEvent;
Consumer<MembershipEvent> c2 = this::onDisappearMemberEvent;
Consumer<MembershipEvent> c3 = c1.andThen(c2);
c3.accept(event);

이런 호출방식은 Pipeline process를 만드는데 유용하게 활용될 것이다.

 

오늘은 짧게 여기 까지 ...

블로그의 정보

지금 당장 해!!!

지금당장해

활동하기