JavaScript의 명령 설계 모드

47465 단어 nodejavascriptwebdev
medium으로 전화해 주세요.
자바스크립트에서 사람들이 가장 즐겨 사용하는 디자인 모델 중 하나는 Command Design Pattern이다. 이 모델은 개발자가 특정한 내용을 요청하는 대상과 원하는 방법을 분리할 수 있도록 한다.
만약 당신이 명령 모드를 처음 듣게 된다면, 이 글을 읽으면, 그것이 무엇인지, 그것이 어떻게 작동하는지, 그리고 왜 우리가 어떤 상황에서 그것을 필요로 하는지 잘 이해할 수 있기를 바랍니다.

명령 설계 모드란?


디자인 모델은 보통 three different types of categories사이에 분류되는데 이런 상황에서 명령 모델은 행위 모델에 속한다.
그렇기 때문에 봉인 대상이 목적이고 이 대상들은 어떤 방법과 내부에서 무슨 일이 일어날지 결정하는 이중책임을 져야 하기 때문이다.
시각적으로 보면 다음과 같이 보일 수 있다.

작업 원리


따라서 본질적으로 통신을 단독 대상으로 나누어 최종 목표를 유지하는 동시에 느슨하게 결합하도록 하는 것이 직책이다.
이러한 모델에 참여하는 참여자는 일반적으로 다음과 같이 불린다.

손님: 네.


클라이언트의 직책은 명령 대상을 만들고 호출 프로그램에 전달하는 것이다.

호출자


호출자가 클라이언트로부터 명령을 받는 대상의 유일한 직책은 명령을 호출(또는 호출)하는 것이다.

수용자


그리고 수신기는 이 명령을 받고 받은 명령에 따라 호출할 방법을 찾는다.

보기에 어떠하다


우리는 명령 모드를 적용하기 전에 코드에서 한 개 이상의 대상이 하는 행동을 방금 보았다.다음은 적용한 모습입니다.
장기적으로 보면, 분명히 거대하고 복잡한 대상은 관리하기 쉬워질 것이다. 왜냐하면 한 대상과 다른 대상 간의 직책은 그들 자신의 개인 세계에서 고립되어 있기 때문에, 난잡하게 함께 놓이지 않기 때문이다.
약속에 따라 Command 대상은 보통 execute이라는 방법을 정의하는데 이 방법은 하나의 방법을 호출하고 약속에 따라 이 방법을 invoker라고 부른다.이러한 방법을 가진 대상을 통상적으로'수신자'라고 부른다.

왜 우리는 명령 모드가 필요합니까


명령 모드를 사용하는 가장 큰 장점은 어떤 일을 하려고 하는 코드와 그것을 처리하는 코드를 분리하는 것이다.코드의 다른 부분에서 자신의 코드가 여러 번 하나의 조작을 처리한다고 생각할 때, 그것을 응용하는 것이 가장 좋다.그럼에도 불구하고 이러한 명령 대상은 우리에게 독특한 장점을 제공했다. 예를 들어 모든 동작/조작을 단독으로 집중적으로 처리할 수 있다.이것은 앞의 예시에서 우리의 대상은 .eat() 명령 하나, .jump() 명령 하나와 .run() 명령 하나만 필요하다는 것을 의미한다.

사용 시기


명령 모드를 최대한 활용할 수 있는 몇 가지 예는 다음과 같습니다.
  • 명령취소/재설정
  • 은 모든 동작/작업의 모든 처리가 명령에 집중되어 있기 때문에 프로그램의 취소/리셋에 적합하다.
  • 원래 요청에 독립적인 라이프 사이클을 가지려면 명령이 필요합니다.
  • 또한 줄을 서려면 다른 시간에 요청을 지정하고 실행하십시오.
  • 작업을 명령취소/명령복구해야 합니다.명령의 실행을 저장하여 그 효과를 반전시킬 수 있다.Command 클래스에서 undo와 Redo를 실현하는 방법은 매우 중요하다.
  • 원래 운영 기반의 고급 운영 체제를 구축해야 합니다.
  • 현실 세계의 예


    이제 개구리 관리자 프로그램을 시작합니다. 이 프로그램은 나이가 들수록 개구리의 목록을 기록하고 관리하는 데 도움을 줍니다.
    이 응용 프로그램에서 우리는 Frog 클래스를 가지고 이를 실현하는 데 유용한 속성과 방법을 실례화할 것이다.
    // Creates and returns a frog api which can help us track activities of each frog
    function createFrog(options) {
      const _opts = {
        name: options.name,
        sex: options.sex,
        age: options.age,
      }
    
      const foodsEaten = []
      const wordsSpoken = []
    
      return {
        getOption(key) {
          return _opts[key]
        },
        getFoodsConsumed() {
          return foodsEaten
        },
        getWordsSpoken() {
          return wordsSpoken
        },
        eat(food) {
          console.log(`Frog "${_opts.name}" is eating: ${food.name} (${food.type})`)
          foodsEaten.push(food)
        },
        talk(words) {
          console.log(words)
          wordsSpoken.push(...words)
        },
      }
    }
    
    위대하다이제 우리는 여러 개의 개구리를 실례화할 수 있다.
    const mikeTheFrog = createFrog({ name: 'mike', sex: 'male', age: 1 })
    const sallyTheOtherFrog = createFrog({ name: 'sally', sex: 'female', age: 4 })
    const michelleTheLastFrog = createFrog({
      name: 'michelle',
      sex: 'female',
      age: 10,
    })
    
    계속해서 frog 애플리케이션을 생생하게 만드는 척하겠습니다.

    index.js


    const api = {
      fetchFrogs: function() {
        return Promise.resolve([
          { id: 1, name: 'mike', sex: 'male', age: 1 },
          { id: 2, name: 'sally', sex: 'female', age: 2 },
          { id: 3, name: 'michelle', sex: 'female', age: 9 },
        ])
      },
      saveToDb: function(frogs) {
        // Just pretend this is actually saving to a real database
        console.log(`Saving ${frogs.length} frogs to our database...`)
        return Promise.resolve()
      },
    }
    
    async function init() {
      try {
        const frogs = await api.fetchFrogs()
        return frogs.map((data) => createFrog(data))
      } catch (error) {
        console.error(error)
        throw error
      }
    }
    
    function createFrogsManager() {
      const frogs = []
    
      return {
        addFrog(frog) {
          frogs.push(frog)
          return this
        },
        getFrogs() {
          return frogs
        },
        getMaleFrogs() {
          return frogs.filter((frog) => {
            return frog.getOption('sex') === 'male'
          })
        },
        getFemaleFrogs() {
          return frogs.filter((frog) => {
            return frog.getOption('sex') === 'female'
          })
        },
        feedFrogs(food) {
          frogs.forEach((frog) => {
            frog.eat(food)
          })
          return this
        },
        save: function() {
          return Promise.resolve(api.saveToDb(frogs))
        },
      }
    }
    
    function Food(name, type, calories) {
      this.name = name
      this.type = type
      this.calories = calories
    }
    
    const fly = new Food('fly', 'insect', 1.5)
    const dragonfly = new Food('dragonfly', 'insect', 4)
    const mosquito = new Food('mosquito', 'insect', 1.8)
    const apple = new Food('apple', 'fruit', 95)
    
    init()
      .then((frogs) => {
        const frogsManager = createFrogsManager()
        // Add each fetched frog to our managing list so we can manage them
        frogs.forEach((frog) => {
          frogsManager.addFrog(frog)
        })
    
        const genders = {
          males: frogsManager.getMaleFrogs(),
          females: frogsManager.getFemaleFrogs(),
        }
        // Lets feed the frogs and then save this new data to the database
        frogsManager
          .feedFrogs(fly)
          .feedFrogs(mosquito)
          .save()
        console.log(
          'We reached the end and our database is now updated with new data!',
        )
        console.log(
          `Fed: ${genders.males.length} male frogs and ${genders.females.length} female frogs`,
        )
        frogsManager.getFrogs().forEach((frog) => {
          console.log(
            `Frog ${frog.getOption('name')} consumed: ${frog
              .getFoodsConsumed()
              .map((food) => food.name)
              .join(', ')}`,
          )
        })
      })
      .catch((error) => {
        console.error(error)
      })
    
    결과:

    우리의 응용 프로그램은 매우 가치가 있게 변했다.
    코드에 명령 설계 모드를 적용하지 않았음을 기억하십시오. 그러나 코드가 아주 잘 작동하고 있습니다. 만약 우리frog 프로그램이 더 커지지 않는다면, 우리도 잘 할 수 있습니다.
    이제 createFrogsManager api를 자세히 살펴보겠습니다.이것은 우리에게 편리한 실용 프로그램을 제공하여 여러 개구리의 활동을 추적하고 시간의 추이에 따라 개구리 목록을 관리하는 api를 제공하는 것을 볼 수 있다.
    그러나 자세히 살펴보면 미래에 잠재적인 문제가 우리를 괴롭힐 수도 있다.
    우리가 본 첫 번째 일은 우리의apicreateFrogsManager이 우리가 사용하고자 하는 방법과 밀접하게 결합되어 있다는 것이다.마지막으로, 우리 코드는 이 인터페이스를 이용하여 직접 그것을 호출하는 방법으로 되돌아오는api에 전적으로 의존합니다.이api는 모든 조작을 호출하고 처리합니다.
    예를 들어 다음과 같은 두 가지 방법으로 되돌아갈 수 있습니다.
    getMaleFrogs() {
      return frogs.filter((frog) => {
        return frog.getOption('sex') === 'male'
      })
    },
    getFemaleFrogs() {
      return frogs.filter((frog) => {
        return frog.getOption('sex') === 'female'
      })
    }
    
    만약 장래에 개구리의 성별을 얻는 경로가 조금씩 바뀐다면?
    따라서 다음과 같이 하십시오.
    function createFrog(options) {
      const _opts = {
        name: options.name,
        sex: options.sex,
        age: options.age,
      }
    
      const foodsEaten = []
      const wordsSpoken = []
    
      return {
        getOption(key) {
          return _opts[key]
        },
        getFoodsConsumed() {
          return foodsEaten
        },
        getWordsSpoken() {
          return wordsSpoken
        },
        eat(food) {
          console.log(`Frog "${_opts.name}" is eating: ${food.name} (${food.type})`)
          foodsEaten.push(food)
        },
        talk(words) {
          console.log(words)
          wordsSpoken.push(...words)
        },
      }
    }
    
    이렇게 된 것이다.
    function createFrog(options) {
      const _opts = {
        name: options.name,
        gender: options.gender,
        age: options.age,
      }
    
      const foodsEaten = []
      const wordsSpoken = []
    
      return {
        getOption(key) {
          return _opts[key]
        },
        getFoodsEaten() {
          return foodsEaten
        },
        getWordsSpoken() {
          return wordsSpoken
        },
        eat(food) {
          console.log(`Frog "${_opts.name}" is eating: ${food.name} (${food.type})`)
          foodsEaten.push(food)
        },
        talk(words) {
          console.log(words)
          wordsSpoken.push(...words)
        },
      }
    }
    
    날이 하루하루 지나가니 모든 것이 평온하다.고소 보고가 없어서 모든 것이 좋습니다.결국, 우리 서버는 전천후에 계속 운행되고 있으며, 그때부터 사용자들은 줄곧 우리 프로그램을 사용하고 있다.
    그리고 한 고객이 2주 후에 고객 서비스부에 전화를 걸어 개구리가 모두 죽었다고 보고했고 그녀의 손실을 우리 플랫폼 탓으로 돌렸다. 왜냐하면 그녀는 우리를 완전히 믿었기 때문이다. 우리의 스마트 알고리즘이 그녀가 정확한 결정을 내리고 개구리가 잘 관리될 수 있도록 도와줄 것이라고 믿었기 때문이다.
    우리의 개발자는 코드에 이 무서운 사건을 일으킬 수 있는 작은 고장이 있는지 확인하기 위해 즉시 통지를 받았고, 디버깅을 요구받았다.
    자세히 검사한 후에 우리는 테스트 코드를 실행했는데, 우리의 코드가 실제로 잘못된 정보를 보고한 것을 발견했다.

    뭐?!그럴 리가 없어!
    이 중 한 개발자는 개구리 대상의 .sex 키가 .gender으로 바뀌었다는 것이 문제라고 지적했다.
    const _opts = {
      name: options.name,
      gender: options.gender,
      age: options.age,
    }
    
    키를 눌러 이전 참조를 사용한 코드를 찾아 변경하여 다시 정상적으로 작동해야 합니다.
    getMaleFrogs() {
      return frogs.filter((frog) => {
        return frog.getOption('gender') === 'male'
      })
    },
    getFemaleFrogs() {
      return frogs.filter((frog) => {
        return frog.getOption('gender') === 'female'
      })
        }
    
    오, 만약 네가 아직 발견하지 못한다면, 우리의 코드에 또 문제가 하나 있다.getFoodsConsumed 내부 접근 방식 createFroggetFoodsEaten으로 변경된 것 같습니다.

    Previous:


    getFoodsConsumed() {
      return foodsEaten
    }
    

    Current:


    getFoodsEaten() {
      return foodsEaten
    }
    
    또 다른 장면에서 만약에 createFrogsManager api의 일부 방법이 이름이 바뀌면 .save에서 .saveFrogs 또는 .getFrogs에서 .getAllFrogs까지 어떻게 해야 합니까?이것은 이러한 방법을 수동으로 사용하는 코드의 모든 부분을 새로운 이름으로 업데이트해야 한다는 것을 의미한다.
    따라서 우리가 예시에서 만난 주요 문제는 변경의 영향을 받은 모든 코드를 복원해야 한다는 것이다.그것은 숨바꼭질 놀이로 변했다.그럴 필요 없어.
    그렇다면 명령 모델은 어떻게 이런 국면을 전환시키는 데 도움을 줄 수 있을까?
    이 글의 첫머리에서 우리는 명령 모드를 언급하여 개발자가 특정한 내용을 요청하는 대상과 필요한 방법을 호출하고자 하는 대상을 분리할 수 있도록 한다.
    이 문장의 첫머리에 우리는 세 명의 참여자를 언급했다.그들은 고객, 호출자, 수용자이다.
    다음 예는 다음과 같습니다.

    명령 방법을 사용하여 createFrogsManager을 재구성합니다.
    function createFrogsManager() {
      const frogs = []
    
      return {
        execute(command, ...args) {
          return command.execute(frogs, ...args)
        },
      }
    }
    
    이것이 바로 우리가 진정으로 필요로 하는 것이다. 왜냐하면 우리는 명령을 완성하게 할 것이다.
    우리는 계속해서 Command 구조 함수를 만들고api의 모든 방법에 구체적인 명령을 만들 것입니다.
    function Command(execute) {
      this.execute = execute
    }
    
    이제 해결되었으니 구체적인 명령을 계속합시다.
    function AddFrogCommand(frog) {
      return new Command(function(frogs) {
        frogs.push(frog)
      })
    }
    
    function GetFrogsCommand() {
      return new Command(function(frogs) {
        return frogs
      })
    }
    
    function FeedFrogsCommand(food) {
      return new Command(function(frogs) {
        frogs.forEach((frog) => {
          frog.eat(food)
        })
      })
    }
    
    function SaveCommand() {
      return new Command(function(frogs) {
        api.saveToDb(
          frogs.map((frog) => ({
            name: frog.name,
            gender: frog.gender,
            age: frog.age,
          })),
        )
      })
    }
    
    이것이 있으면 우리는 이렇게 그것을 사용할 수 있다.
    function Food(name, type, calories) {
      this.name = name
      this.type = type
      this.calories = calories
    }
    
    const mikeTheFrog = createFrog({
      name: 'mike',
      gender: 'male',
      age: 2,
    })
    
    const sallyTheFrog = createFrog({
      name: 'sally',
      gender: 'female',
      age: 1,
    })
    
    const frogsManager = createFrogsManager()
    frogsManager.execute(new AddFrogCommand(mikeTheFrog))
    frogsManager.execute(new FeedFrogsCommand(new Food('apple', 'fruit', 95)))
    frogsManager.execute(new FeedFrogsCommand(new Food('fly', 'insect', 1)))
    frogsManager.execute(new AddFrogCommand(sallyTheFrog))
    frogsManager.execute(new SaveCommand())
    const updatedFrogs = frogsManager.execute(new GetFrogsCommand())
    
    결과:

    비주얼에서 수신기는 공백이다. 자바스크립트에서 모든 함수와 대상은 기본적으로 명령 자체이기 때문이다. 우리는 .execute에서 명령을 직접 호출하여 이 점을 보여 주었다.
    function createFrogsManager() {
      const frogs = []
    
      return {
        execute(command, ...args) {
          return command.execute(frogs, ...args)
        },
      }
    }
    

    결론


    이 글은 이것으로 끝냅니다!나는 네가 이것이 가치가 있다는 것을 발견하고 미래에 더욱 많은 것을 기대하길 바란다.
    medium으로 전화해 주세요.

    좋은 웹페이지 즐겨찾기