디자인 패턴: JS 기능 체인

기능 사슬: 구현



Javascript에서 직렬화 가능 체인 가능 기능 API 작성.

아래의 모든 작업은 여기functional chain builder에서 찾을 수 있습니다. 작은 API를 생성할 수 있는 기성품 및 재사용 가능한 npm 모듈입니다.

소개



저는 오랫동안 연결 가능한 API가 우아하고 설명적이라고 생각했습니다.

그리고 재미있는 실험으로 기능적이고 상태 비저장 구현을 가지고 놀기 시작했습니다.

사슬



다음은 내가 생각하는 API의 예입니다.

const operation = multiplyBy(2)
  .and.subtract(6)
  .and.divideBy(2);

operation(33); // => 30

결과는 다른 명령을 순서대로 적용하는 재사용 가능한 기능이어야 합니다.

직렬화



작업을 즉시 적용하는 대신 이 API는 함수를 반환하도록 설계되었습니다. 그 이유는 직렬화를 허용하기 위해서입니다.

다음은 어떻게 보이는지에 대한 예입니다.

analyse(operation);

// output =>
[
  { multiplyBy:  [2] },
  { subtract: [6]},
  { divideBy: [2] }
]

직렬화의 이점은 무엇입니까?

테스트



직렬화는 테스트에 도움이 될 수 있습니다. 작업이 정확하다고 주장할 수 있습니다. 종단 간 테스트를 더 간단한 단위 테스트로 대체할 수 있음\

네트워킹



직렬화된 작업은 유선으로 전송될 수 있는 작업으로 체인의 사용 사례를 확장합니다.

자바스크립트 악용



이를 가능하게 하는 언어 기능을 간단히 살펴보겠습니다.

함수는 일급 객체입니다.



A programming language is said to have First-class functions when functions in that language are treated like any other variable



출처: mozilla.org

이것이 우리에게 의미하는 바는 무엇입니까?
  • 함수를 인수로 전달할 수 있습니다.
  • 속성을 함수로 설정할 수 있음

  • 범위 지정 및 폐쇄



    클로저는 설명하는 것보다 사용하기가 더 간단합니다. 하지만 우리에게 중요한 것은 다음과 같습니다.

    함수가 다른 함수를 생성하면 해당 함수는 생성자의 범위에 액세스할 수 있습니다. 그것은 차례로 새로운 기능 자체를 생성할 수 있고, 그런 다음 다시, 그리고 다시... 체인을 구축할 수 있습니다.

    체인 구현



    API 정의



    실제로 체인을 작성하기 전에 API를 정의해야 합니다.

    const API = {
      add(val) {
        return num => num + val
      },
    
      subtract(val) {
        return num => num - val
      },
    
      multiplyBy(val) {
        return num => num * val
      },
    
      divideBy(val) {
        return num => num / val
      }
    }
    

    이것은 매우 간단합니다. 각 메서드는 원하는 작업을 적용할 함수를 반환합니다.

    래퍼 함수 만들기



    함수에서 함수를 반환하는 아이디어에 대해 논의했습니다. 따라서 체인을 수신하고 완료된 작업을 반환하는 기본 함수를 만들어 보겠습니다.

    function Wrap(chain = []) {
        let compute = (num) => {
            // Iterate through the chain and applies the calculations
            return chain.reduce((mem, fn) => fn(mem), num);
        }
    
        return compute;
    }
    

    이 시점에서 우리는 체인에 어떤 것도 추가할 수단이 없습니다. 따라서 compute 함수에 메서드를 추가해 보겠습니다. 이전에 정의된 각 메서드에 대해 하나씩입니다.

    for (let key in API) {
      const fn = API[key];
      compute[key] = () => {
         ...
      }
    }
    

    우리는 이미 체인의 예상 결과인 함수를 반환해야 한다는 것을 알고 있습니다. 우리는 또한 이 함수가 더 많은 함수를 연결할 수 있어야 한다는 것을 알고 있습니다.

    대부분의 사용자는 이것이 오는 것을 보았고, 정확히 그렇게 하는 우리의 Wrap 를 반환할 수 있습니다. 체인 연결은 확장된 체인을 제공하여 이루어집니다.

    function Wrap(chain = []) {
        let compute = (num) => {
          // Iterate through the chain and applies the calculations
          return chain.reduce((mem, fn) => fn(mem), num);
        }
    
        for (let key in API) {
          const fn = API[key];
          compute[key] = (num) => {
            return Wrap([ ...chain, fn(num) ]);
          }
        }
    
        return compute;
    }
    

    현재 이 사용법은 다음과 같이 작동합니다.

    const operation = Wrap()
      .multiplyBy(2)
      .subtract(6)
      .divideBy(2);
    
    operation(33); // => 30
    

    API 꾸미기



    이제 작동하는 연결 가능한 API가 있습니다. 그러나 모든 체인 앞에 Wrap()를 붙일 필요는 적절하지 않습니다.

    사용자 친화적 방법 내보내기



    우리는 API의 방법 중 하나를 통해 체인을 시작할 수 있기를 원합니다. 이를 달성하는 쉬운 방법은 랩이 포함된 모듈이 해당 메서드를 내보내도록 하는 것입니다.

    
    // (API Object)
    
    // (Wrap function)
    
    module.exports = Object
        .keys(API)
        .reduce((res, key) => {
          const fn = API[key];
          res[key] = (...params) => Wrap([ fn(...params) ]);
          return res;
        }, {});
    

    우리는 기본적으로 메서드 내부에 초기 랩을 숨깁니다.

    현재 사용량은 다음과 같습니다.

    const { multiplyBy } = require('./mychain');
    
    const operation = multiplyBy(2)
      .subtract(6)
      .divideBy(2);
    
    operation(33); // => 30
    

    이미 훨씬 좋아 보입니다.

    시맨틱 추가



    초기 디자인의 일부는 각 체인 구성원 사이에 선택적and 키워드를 포함하는 것이었습니다. 그 필요성에 대해서는 논쟁의 여지가 있지만 과학을 위해 그렇게 합시다.

    구현은 이보다 더 간단할 수 없습니다.

    function Wrap(chain = []) {
        let compute = (num) => { ... }
    
        for (let key in API) {
          const fn = API[key];
          compute[key] = (num) => { ... }
        }
    
        // Semantics of choice
        compute.and = compute;
        compute.andThen = compute;
        compute.andThenDo = compute;
    
        return compute;
    }
    

    예상되는 사용법은 다음과 같습니다.

    const operation = multiplyBy(2)
      .and.subtract(6)
      .andThen.divideBy(2);
    
    operation(33); // => 30
    

    다음 단계: 직렬화



    내 기능 체인 기사의 1부를 읽어 주셔서 감사합니다.

    짧게 유지하기 위해 별도의 기사에서 직렬화 주제를 계속하겠습니다.

    연결 가능한 API를 구축한 경험이 있는 사람이 있다면 귀하의 접근 방식과 사용 사례를 듣고 싶습니다.

    건배,

    패트릭

    좋은 웹페이지 즐겨찾기