엔버의 의존 주입 시스템은 어떻게 작동합니까?

35850 단어 emberdijavascript
오리지널 게시물 (더 좋은 형식) @ nullvoxpopuli.com

왜?


내가 은백과 처음 접촉한 사람에게서 들은 가장 흔한 일 중 하나,
일반적으로 프로그래밍 초보자나 다른 전단 생태계에서 왔다
(특히 React와 Vue), 그들은 엠버의 의존이 주입되었다고 생각한다
이 시스템은 너무 복잡하고 신기하다--
주입된 서비스가 어디에서 왔는지 설명하거나 알기 어렵다.
1. 나도 배에서 그 작업 원리를 진정으로 깊이 이해할 때까지
그리고 나는 왜 주입에 의존하는 것이 존재하는지 이해하기 시작했다. 그리고
그것은 실제로는 전혀 없는 것보다 더 간단하다.

무엇이 주입에 의존합니까?


근거Wikipedia

dependency injection is a technique in which an object receives other
objects that it depends on.


이렇게
그래서...이것은 주입에 의존하는 것입니까?
let foo = new Foo()

let bar = new Bar(foo);
그래.
의존항 주입의 중요한 의의는 일반적으로 어떻게 관리 대상에서 나온다
다른 객체를 수신합니다.

왜 의존 주입을 사용합니까?


나 개인적으로는 두 가지 이유가 있다.
  • 어플리케이션 상태(데이터 및 기능)를 구성 요소
  • 간에 쉽게 공유
  • 테스트가 쉽고 개별적으로 수행할 수 있음
  • #1의 경우 여러 가지 방법으로 구성 요소 간에 상태를 공유할 수 있습니다.
    의존 주입은 이 상태에 집중적인 패턴과 위치를 제공한다
    그리고 인체공학과 빛에 부합되는 방식으로 이런 상태와 상호작용한다.
    #2의 경우 한두 마디로 요약하기가 더 어렵습니다.
    응용 프로그램의 전체 구조, 응용 프로그램의 크기, 그리고 어떤 유형의 응용 프로그램을 분석합니다.
    테스트를 할 때 사물은 가치를 제공한다.예를 들면, 네가 어떤 행위를 하고 있다고 가정하면
    외부 API와의 상호 작용의 경우Star Wars JSON api
    혹은 로봇을 구축하기 위한 게임과 상호작용을 할 수도 있다.
    이 모든 기능을 구성 요소에 구축합니다. 왜 너무 추상적입니까?
    그러나 너도 이 기능을 하나의 서비스에 구축할 수 있다. 혹은 '단지 다른 것' 이다.
    클래스, 어셈블리는 다음과 같이 클래스를 사용합니다.
    class MyComponent {
      constructor() {
        this.api = new StarWarsApi();
      }
    }
    
    let myComponent = new MyComponent();
    
    이것은 위대한 첫걸음이다!StarWarsApi 자체 테스트가 가능하므로 필요 없음
    구성 요소와 연결해야 합니다.그러나, 구성 요소는 상반된 기능을 가지고 있습니다
    문제는 StarWarsApi에 따라 달라지고 테스트를 진행할 방법이 없다는 것이다.MyComponent의 행위는 사용되지 않았다StarWarsApi의 실제 실현.
    해결 방안은 주입에 의존하는 것이다.StarWarsApi의 구체적인 실현을 인터페이스로 간소화하다
    (우리가 관심 있는 방법 목록), 테스트 과정에서 우리는 교환할 수 있다StarWarsApi 중 모든 동일한 방법을 가진 모조품이 있다.
    class MyComponent {
      constructor(api) {
        this.api = api;
      }
    }
    
    let fakeApi = { /* fake stuff here */ }
    let myComponent = new MyComponent(fakeApi);
    
    이 화제에 대해 많은 정보가 있다고 생각합니다this StackOverflow Answer
    요약:

    So, to cut a long story short: Dependency injection is one of two ways of how
    to remove dependencies in your code. It is very useful for configuration
    changes after compile-time, and it is a great thing for unit testing
    (as it makes it very easy to inject stubs and / or mocks).


    이것은 나로 하여금 소프트웨어 공학과 체계 구조의 모든 관점을 떠올리게 했다
    개술: 테스트를 더욱 쉽게 한다.
    만약 우리가 이전에 잘못을 저질렀다면, 스스로 그 속에서 교훈을 받아들이는 것을 허락하지 않을 것이다
    우리는 지금 우리의 동료와 미래의 자신을 위해 고된 테스트를 진행하고 있다
    우리 동료(그리고 우리 자신도!)상처
    이것은 테스트의 중요성과 철학에 어긋나기 쉽다
    테스트 구동 구조지만 이것은 또 다른 시대의 주제이다.

    Ember에 의존 주입은 어떻게 작동합니까?


    가장 좋은 묘사 방식은 우리가 어떻게 창조할 것인지를 먼저 보여주는 것이라고 나는 생각한다
    우리 자신의 의존 주입 시스템은 처음부터 시작한다.
    이것은 아래에서 위로 올라가는 방법으로 우리가 최소치부터 시작한다는 것을 의미한다
    우리의 전진에 따라 점점 더 많은 행위가 증가한다.우선, 우리는 약간의 정의를 필요로 한다
    조항과 목표를 설정하기 때문에 우리는 같은 페이지에 있다.
    명명법:
  • 서비스: 상태와/또는 행위의 명칭 저장통(일반적으로 클래스 실례).
  • 주입: 서비스에 대한 인용을 정의하는 행위
  • 용기: 모든 서비스에 대한 인용을 저장하는 대상
  • 대상:
  • 어느 곳에서든 인용할 수 있는 서비스
  • 서비스 하나singleton
  • 서비스는 서로 인용할 수 있다
  • 글로벌 네임스페이스에 액세스할 수 없음
  • 이것은 의존 주입의 조상으로 여겨질 수 있다. 왜냐하면 의존 주입이 존재하기 때문이다
    모듈 역할 영역에서의 공유container 대상은 여전히 저희에게
    앞의 세 가지 목표를 실현하다.
    // app.js
    let container = {};
    
    function bootApp() {
      initializeServices();
    
      container.bot.begin();
    }
    
    class Bot {
      begin() {
        let nextMove = container.ai.getMove();
    
        container.ui.sendKeyPress(nextMove);
      }
    }
    
    function initalizeServices() {
      container.ai = new AI();
      container.bot = new Bot();
      container.ui = new UI();
    }
    
    
    bootApp();
    
    이 코드의 작동 상태를 보려면 this CodeSandBox
    다중 파일 환경에서는 파일 간의 동일한 모듈 범위를 액세스할 수 없습니다.
    // app.js
    import Bot from './bot';
    import AI from './ai';
    import UI from './ui';
    
    let container = {};
    
    function bootApp() {
      initializeServices();
    
      container.bot.begin();
    }
    
    function initializeServices() {
      container.ai = new AI(container);
      container.bot = new Bot(container);
      container.ui = new UI(container);
    }
    
    // bot.js
    export default class Bot {
      constructor(container) {
        this.container = container;
      }
    
      begin() {
        let nextMove = this.container.ai.getMove();
    
        this.container.ui.sendKeyPress(nextMove);
      }
    }
    
    
    이 코드의 작동 상태를 보려면 this CodeSandBox
    그러나 프레임워크나 라이브러리 개발자로서 사용자/응용 프로그램 개발자
    매번 용기를 분배하는 것은 인체공학에 부합되지 않는다는 것을 기억해라.
    // app.js
    // same as before
    
    // service.js
    export default class Service {
      constructor(container) {
        this.container = container;
      }
    }
    
    // bot.js
    import Service from './service';
    
    export default class Bot extends Service {
      begin() {
        let nextMove = this.container.ai.getMove();
    
        this.container.ui.sendKeyPress(nextMove);
      }
    }
    
    이것은 더욱 좋은 점이다. 우리는 이미 일부 견본을 추상화했지만, 여전히 있다
    "신기한 속성"container - 이것은 보통 대상을 대상으로 프로그래밍한다
    적절하거나 불완전한 추상이 결여되어 부정적인 평판을 얻을 수도 있다.

    A bad abstraction is worse than no abstraction


    그러니까 decorator로 정리해 봅시다.
    // app.js
    // same as before
    
    // service.js
    let CONTAINER = Symbol('container');
    
    export default class Service {
      constructor(container) {
        // the container is now set on a symbol-property so that app-devs don't
        // directly access the container. We want app-devs to use the abstraction,
        // which we're aiming to be more ergonamic
        this[CONTAINER] = container;
      }
    }
    
    // this is a decorator, and would be used like `@injectService propertyName`
    // where target is the class, name would be "propertyName", and descriptor is the
    // property descriptor describing the existing "propertyName" on the class that is
    // being decorated
    //
    // For more information on decorators, checkout the above linked decorator plugin
    // for babel.
    export function injectService(target, name, descriptor) {
      return {
        configurable: false,
        enumerable: true,
        get: function() {
          if (!this[CONTAINER]) {
            throw new Error(`${target.name} does not have a container. Did it extend from Service?`);
          }
    
          return this[CONTAINER][name];
        }
      }
    }
    
    // bot.js
    import Service { injectService } from './service';
    
    export default class Bot extends Service {
      @injectService ai;
      @injectService ui;
    
      begin() {
        let nextMove = this.ai.getMove();
    
        this.ui.sendKeyPress(nextMove);
      }
    }
    
    이 코드의 작동 상태를 보려면 this CodeSandBox
    이 방법을 사용하면 각 서비스를 이름별로 참조할 수 있지만 이제 새로운 문제가 발생했습니다.
    프레임워크 개발자로서 우리는 어떻게 서비스 속성과 서비스 종류가 일치하는지 확보합니까?
    현재의 실현에서 우리는 container 대상에 임의로 값을 부여하고ui, aibot.이것은 사용자 공간에서, 우리는 줄곧 이러한 속성이 무엇인지 알고 있다
    컨테이너 위에 있어요.
    이것이 공약의 역할이다.
    프레임워크/라이브러리의 작성자로서 우리는 서비스가 반드시services/ 프로젝트의 폴더입니다.
    let container = {};
    
    function bootApp() {
      initializeServices();
    
      container.bot.begin();
    }
    
    function initializeServices() {
      for (let [name, AppSpecificService] of detectedServices) {
       container[name]  = new AppSpecificService(container);
      }
    }
    
    단, 모듈 기반 자바스크립트에 익숙하면 detectedServicesservices/ 폴더의 서비스를 알고 이름을 알아야 합니다.
    구축할 때, CLI는 실행할 때 우리의 프레임워크를 도울 수 있다.
    Ember에서 이 단계는 ember-resolver에서 처리됩니다.
    그리고 따르기requirejs,
    defines modules 중 어느 것AMD
    격식--지금 우리는 걱정할 필요가 없다.
    프레젠테이션 목적으로 bundler와 CLI가 구성되었음을 "말합니다.
    모듈에 대한 상대 파일 경로 매핑을 생성합니다.
    let containerRegistry = {
      'services/bot': import('./services/bot'),
      'services/ai': import('./services/ai'),
      'services/ui': import('./services/ui'),
    }
    
    그러면 우리app.js는 아마 이렇게 될 것이다.
    let knownServices = Object.entries(containerRegistry);
    let container = {};
    
    function bootApp() {
      initializeServices();
    
      container.bot.begin();
    }
    
    function initializeServices() {
      for (let [fullName, ServiceModule] of knownServices) {
        let name = fullName.replace('services/', '');
        let DefaultExport = ServiceModule.default;
    
        container[name]  = new DefaultExport(container);
      }
    }
    
    현재 우리 문서에서, 우리는 서비스의 파일 이름이 무엇이든지 쓸 수 있다
    이 서비스의 실례를 가리키는 속성의 이름이 될 것입니다
    전화번호container.
    지금 만약 우리가 우리의 서비스가 타성적으로 실례화되기를 희망한다면, 우리는 부정적인 영향을 미치지 않을 것이다. 어떻게 해야 하는가
    만약 우리가 필요로 하지 않는다면, 상호작용 기준 테스트의 시간에 영향을 줄 수 있습니까?
    지금까지 우리container는 일반적인 고물이었다.사용 가능Proxy
    let knownServices = Object.entries(containerRegistry);
    let registry = {};
    
    let container = new Proxy(registry, {
      get: function(target, propertyName) {
        if (target[propertyName]) {
          return target[propertyName];
        }
    
        let FoundService = lookupService(propertyName);
    
        target[propertyName] = new FoundService(container);
    
        return target[propertyName];
      }
    });
    
    function lookupService(serviceName) {
      let serviceModule = Object.entries(knownServices).find((serviceInfo) => {
        let [ servicePath, serviceModule ] = serviceInfo;
    
        let name = servicePath.replace('services/', '');
    
        if (serviceName === name) {
          return serviceModule;
        }
      });
    
      if (!serviceModule) {
        throw new Error(`The Service, ${serviceName}, was not found.`);
      }
    
      return serviceModule.default;
    }
    
    function bootApp() {
      // initialization now happens on-demand
      container.bot.begin();
    }
    
    최종 구현을 보려면 this CodeSandBox

    은백이는 배후에서 무엇을 합니까?


    Ember는 귀하로부터 거의 모든 상술한 내용을 추출하여
    서비스 이름을 서비스 실례에 비추고 이 실례에 접근합니다
    서비스, 용기 감지 대상을 만듭니다.
    이 용기에 관해서 가장 중요한 것은
    포함된 파일을 제공하며 앙보 내부에서 이를'소유자'라고 부른다. 아래와 같다.
    각 반의 첫 번째 논점.
    그래서 만약 당신이 자신의'종류'대상을 가지고 싶다면, 아마도 이것은 일련의 습관일 것이다
    API, 캔버스 또는 WebGL과 같은 외부 객체와 상호 작용하는 객체
    혹은.정말이야!,엠버 쓸 수 있어요.
    컨테이너
    Ember는 내부에서 서비스, 라우팅, 컨트롤러, 구성 요소, 도우미,
    수정기, 하지만 잿더미가 하고 있는 일을 하려면, 응용 프로그램 어딘가에 있습니다.
    // maybe in a Route's beforeModel hook
    let owner = getOwner(this);
    owner.register(
      /*
        full name in the format:
        namespace:name
      */
      'webgl:renderer',
      /* class */
      Renderer
    );
    
    현재, 구성 요소에서 어떻게 접근하시겠습니까?이것은 서비스가 아니기 때문에
    서비스 인테리어 안돼.우선 서비스 장식사가 어떤 모습인지
    // abridged version of the @service decorator
    //
    //
    // NOTE: ember convention is:
    //   import { inject as service } from '@ember/service';
    export function inject(target, name, descriptor) {
      return {
        configurable: false,
        enumerable: true,
        get: function() {
          let owner = getOwner(this);
    
          return owner.lookup(`service:${name}`);
        }
      }
    }
    
    이렇게 하면 @service api가 있을 때 이름 공간은
    너, 그리고 용기에서 service:api 전체 이름을 찾아라.
    상기 내용을 이해하면 우리는 자신의 장식기를 제작할 수 있다. 그러면 우리는 우리의
    독신자
    export function webgl(target, name, descriptor) {
      return {
        configurable: false,
        enumerable: true,
        get: function() {
          let owner = getOwner(this);
    
          return owner.lookup(`webgl:${name}`);
        }
      }
    }
    
    따라서 응용 프로그램 어디에서나 다음 구성 요소를 사용할 수 있습니다.
    class MyComponent extends Component {
      @webgl renderer;
    }
    

    "이것뿐이야, 동료들아!"


    ember 의존 주입의 실현을 깨달았을 때 나는
    간단하다그것은 기본적으로 전 세계적인 상점으로, 거기에서 클래스를 저장할 수 있는 실례이다
    전 세계 상점에 저장되어 있으며, 응용 프로그램의 다른 곳에서 인용됩니다.
    만약 이곳의 일이 간단하지 않다면, 나에게 알려주세요!제가 조정을 좀 했으면 좋겠어요.
    쉬울 때까지.
    나는 이 모델을 매우 좋아한다. 왜냐하면 이것은 현식 전달 인용의 수요를 피하기 때문이다
    전체 응용 프로그램에서 사용하고 싶은 모든 대상입니다.대신 여신 요약
    컨테이너를 통해 작성된 모든 객체에 컨테이너 객체 전달
    (주로 구성 요소와 서비스이지만 사용자 정의 클래스를 사용할 수도 있다).

    면책 성명


    의존 주입은 큰 화제일 수도 있고 많은 기능을 실현했다.
    "이 프레젠테이션은""완벽한 기능""프레젠테이션이 될 계획이 없음"
    의존 주입 실현.

    정보


    전공 분야에서 저는 React에서 전단 개발을 시작했습니다.
    국가 관리 부문에는 Redux와 MobX만 있지만, 나는
    Redux와 협력할 권리가 있으며 최종적으로 React의 상하문 공급자/사용자와 합작할 권리가 있다
    패턴.React의 상하문과 Ember의 상하문 사이에 약간의 중첩이 있다
    서비스, 그러나 그것들은 근본적으로 다르다. 이것은 아마도 우리의 화제일 것이다
    다음에 하자.
    지금, 나는 거의 매일 은백과 함께 일하는 보수를 받을 수 있다.
    프레임 및
    나는 전 세계와 그것들을 공유하기를 갈망한다.
    이것은 트위터의 몇몇 대화에서 얻은 계발이며, 동시에 이렇게 하지 않으려고 시도하고 있다.
    웹 프레임워크로 구축
    Artificatial Intelligence to play a game

    도구책

  • TC39 Decorator Proposal
  • Ember Documentation on Dependency Injection
  • 좋은 웹페이지 즐겨찾기