1. 명령형 프로그래밍 vs 선언형 프로그래밍
1) 명령형 프로그램
- 예문
public class ImperativeProgrammingExample {
public static void main(String[] args){
// List에 있는 숫자들 중에서 4보다 큰 짝수의 합계 구하기
List<Integer> numbers = List.of(1, 3, 6, 7, 8, 11);
int sum = 0;
for(int number : numbers){
if(number > 4 && (number % 2 == 0)){
sum += number;
}
}
System.out.println(sum);
}
}
- List에 포함된 숫자들을 for문을 이용해서 순차적으로 접근한다
- if 문으로 특정 조건에 맞는 숫자들만 sum 변수에 더해서 합계를 구한다
- 코드가 어떤식으로 실행되어야 하는지에 대한 구체적인 로직들이 코드 안에 그대로 나타난다
2) 선언형 프로그램
- 예문
public class DeclarativeProgramingExample {
public static void main(String[] args){
// List에 있는 숫자들 중에서 4보다 큰 짝수의 합계 구하기
List<Integer> numbers = List.of(1, 3, 6, 7, 8, 11);
int sum =
numbers.stream()
.filter(number -> number > 4 && (number % 2 == 0))
.mapToInt(number -> number)
.sum();
System.out.println("# 선언형 프로그래밍: " + sum);
}
}
- Java에서 선언형 프로그래밍 방식을 이해하기 위한 가장 적절한 예는 바로 Java 8부터 지원하는 Stream API이다
- List에 포함된 숫자들을 처리하는 것은 명령형 코드와 동일하지만 처리 방식은 전혀 다르다
- Java Stream API를 사용하기 때문에 코드 상에 보이지 않는 내부 반복자가 명령형의 for문을 대체하고 있다
- filter() 메서드(Operation)가 if 문을 대신해서 조건에 만족하는 숫자를 필터링하고 있다
- Stream API를 사용하는 것이 선언형 프로그래밍 방식인 이유는
- 명령형 프로그래밍 방식으로 작성된 코드는 위에서 아래로 순차적으로 실행한다
: for문을 만나게 되면 for문 내부로 진입하게 된다
: if문을 만나면 if 문의 조건을 판단한 후에 조건에 맞으면 sum 변수에 숫자를 더한 뒤에 다시 for 문을 반복한다
- Stream API를 사용한 선언형 코드는 numbers.stream().filter().mpaToInt().sum()과 같은 메서드 체인이 순차적으로 실행이 되지 않는다
: filter()나 mapToInt() 메서드에 파라미터를 전달하기 때문에 즉시 호출되는 것처럼 생각될 수 있지만 그렇지 않다
: Stream의 경우 최종 연산을 수행하는 메서드를 호출하지 않으면 앞에서 작성한 메서드 체인들이 실행 되지 않는다
: Stream의 메서드 체인(중간 연산)에는 작업을 해 달라고 선언(요청)하는 람다 표현식만 넘겨준다
: 최종 연산이 호출될 때 전달 받은 람다 표현식을 기반으로 동작을 수행한다 - 선언형 프로그래밍 방식은 개발자가 로직을 모두 작성하지 않는다
- 필요한 동작들을 람다 표현식으로 정의(선언)하고 구체적인 동작 수행은 Operation(연산) 메서드 체인에 위임한다
2. 리액티브 프로그래밍의 구조
1) 예제 1
package com.codestates.example;
import reactor.core.publisher.Mono;
public class HelloReactiveExample01 {
public static void main(String[] args) {
Mono<String> mono = Mono.just("Hello, Reactive");
mono.subscribe(message -> System.out.println(message));
}
}
- Publisher는 데이터를 emit하는 역할을 하고 Subsciber는 Publisher가 emit한 데이터를 전달 받아서 소비하는 역할을 한다
- Puhlisher의 역할을 하는 것이 Mono 이다
- Subscriber의 역할을 하는 것이
: subscribe() 메서드 내부에 정의된 람다 표현식인 message ->System.out.println(message) 이다 - Java의 Stream에서 메서드 체인 형태로 사용할 수 있듯이 리액티브 프로그래밍에서도 메서드 체인을 구성할 수 있다
2) 예제 2
package com.codestates.example;
import reactor.core.publisher.Mono;
public class HelloReactiveExample02 {
public static void main(String[] args) {
Mono
.just("Hello, Reactive")
.subscribe(message -> System.out.println(message));
}
}
- 예제1의 코드를 하나의 메서드 체인 형태로 표현한 코드이다
- Java의 Stream API와 리액티브 프로그래밍이 선언형 프로그래밍 방식으로 구성되는 공통점을 가지고 있지만 차이점이 더 많이 존재한다
3) 예제 3
package com.codestates.example;
import org.springframework.http.converter.json.GsonBuilderUtils;
import reactor.core.publisher.Flux;
import java.util.List;
public class ReactiveGlossaryExample {
public static void main(String[] args) {
Flux
.fromIterable(List.of(1,3,6,7,8,11))
.filter(number -> number > 4 && (number % 2 == 0))
.reduce((n1, n2) -> n1 + n2)
.subscribe(System.out::println);
}
}
3-1) 리액트 프로그램밍 주요 용어
- Publisher
- 데이터를 내보내는 주체를 의미한다
- 예제3에서는 Flux가 Publisher이다
- Emit
- Publisher가 데이터를 내보내는 것을 Emit 이라고 한다
- Subscriber
- Publisher가 emit한 데이터를 전달 받아서 소비하는 주체를 의미한다
- 예제3에서는 subscribe(System.out::println) 중에서 System.out::println이 Subscriber에 해당된다
- 람다 표현식을 메서드 레퍼런스로 축약하지 않았다면 람다 표현식 자체가 Subscriber에 해당된다
- Subscribe
- 구독을 의미한다
- subscribe를 사용하면 시스템은 자동으로 subscriber를 생성한다
- 예제3에서 subscribe() 메서드를 호출하면 subscriber를 생성하여 Publisher가 보내 온 데이터를 출력하게 된다
- Signal
- Publisher가 발생시키는 이벤트를 의미한다
- 예제3에서 subscribe() 메서드가 호출되면 Publisher인 Flux는 숫자 데이터를 하나씩 하나씩 emit 한다
- 숫자 데이터를 하나씩 emit하는 자체를 리액티브 프로그래밍에서는 이벤트가 발생하는 것으로 간주한다
- 이벤트 발생을 다른 컴포넌트에게 전달하는 것을 Signal을 전송한다라고 표현한다
- Operator
- 리액티브 프로그래밍에서 어떤 동작을 수행하는 메서드를 의미한다
- 예제3에서 fromIterable(), filter(), reduce() 등의 메서드 하나 하나를 Operator라고 한다
- 연산자라고도 한다
- Sequence
- Operator 체인으로 표현되는 데이터의 흐름을 의미한다
- 예제3에서 Operator 체인으로 작성된 코드 자체를 하나의 Sequence라고 볼 수 있다
- Upstream / Downstream
- Sequence 상의 특정 Operator를 기준으로 위쪽의 Sequence 일부를 Upstream이라고 한다
- 아래 쪽 Sequence 일부를 Downstream이라고 한다
- 예제3에서 filter() Operaotr를 기준에서 보면 filter() Operator 위 쪽의 fromIterable()은 Upstream 이다
- filter() Operator 아래 쪽의 reduce() Operator는 Downstream 이다
▶ declarative programming : https://www.techtarget.com/searchitoperations/definition/declarative-programming
What Is Declarative Programming? Definition from SearchITOperations
Declarative programming focuses on the outcome, not the journey. Learn how it works and how it compares to imperative programming through examples.
www.techtarget.com
▶ Declarative programming : https://en.wikipedia.org/wiki/Declarative_programming
Declarative programming - Wikipedia
Programming paradigm based on modeling the logic of a computation In computer science, declarative programming is a programming paradigm—a style of building the structure and elements of computer programs—that expresses the logic of a computation witho
en.wikipedia.org
'Spring WebFlux' 카테고리의 다른 글
Spring WebFlux - 리액티브 프로그래밍(Reactive Programming) (0) | 2022.08.10 |
---|