Development Tip

JavaScript의 구성, 상속 및 집계

yourdevel 2020. 12. 25. 10:34
반응형

JavaScript의 구성, 상속 및 집계


온라인에서 구성과 상속에 대한 많은 정보가 있지만 JavaScript로 적절한 예를 찾지 못했습니다. 아래 코드를 사용하여 상속을 보여줍니다.

function Stock( /* object with stock names and prices */ ) {
    for (var company_name in arguments[0]) {
        // copy the passed object into the new object created by the constructor
        this[company_name] = arguments[0][company_name]; 
    }
}

// example methods in prototype, their implementation is probably redundant for
// this question, but list() returns an array with toString() invoked; total()
// adds up the stock prices and returns them. Using ES5 feature to make
// inherited properties non-enumerable 

Stock.prototype =  {
    list: function () {
        var company_list = [];
        for (var company_name in this)
            company_list.push(company_name);
        return company_list.toString();
    },
    total: function () {
        var price_total = 0;
        for (var company_name in this)
            price_total += this[company_name];
        return '$' + price_total;
    }
};

Object.defineProperties(Stock.prototype, {
    list: { enumerable: false },
    total: { enumerable:false }
});

var portfolio = new Stock({ MSFT: 25.96, YHOO: 16.13, AMZN: 173.10 });
portfolio.list();  // MSFT,YHOO,AMZN
portfolio.total(); // $215.19

(코드를 더 작게 만들려면 다음과 같이 메소드 구현을 생략 할 수 있습니다 Stock.total = function(){ /* code */ }. OOP의 많은 상황에서 구성이 선호된다면 JavaScript를 사용하는 대부분의 사람들은 왜 프로토 타입과 상속만을 사용하는 것처럼 보입니까? 온라인에서 JavaScript의 구성에 대한 많은 정보를 찾지 못했고 다른 언어에서만 찾았습니다.

누군가가 위의 코드를 사용하여 구성 및 집계를 보여주는 예제를 줄 수 있습니까?


언어는 구성과 상속을 다룰 때 관련이 없습니다. 클래스가 무엇인지, 클래스의 인스턴스 가 무엇인지 이해한다면 필요한 모든 것이 있습니다.

구성은 단순히 클래스가 다른 클래스 로 구성된 경우 입니다. 또는 다른 말로 말하자면, 객체의 인스턴스는 다른 객체의 인스턴스에 대한 참조를 가지고 있습니다.

상속은 클래스가 다른 클래스의 메서드와 속성을 상속하는 경우입니다.

A와 B라는 두 가지 기능이 있다고 가정 해 보겠습니다. A와 B의 일부 또는 전부를 포함하는 세 번째 기능인 C를 정의하려고합니다. C를 B와 A에서 확장 할 수 있습니다.이 경우 C는 모든 B를 갖습니다. A는 C isAB와 A 이기 때문에 , 또는 C의 각 인스턴스가 A의 인스턴스와 B의 인스턴스를 갖도록 만들고 해당 기능에 대한 항목을 호출 할 수 있습니다. 후자의 경우 각 인스턴스 C는 사실상 B의 인스턴스와 A의 인스턴스를 래핑합니다.

물론 언어에 따라 2 개의 클래스에서 확장 된 클래스를 가지지 못할 수도 있습니다 (예 : Java는 다중 상속을 지원하지 않음). 이는 개념과 관련이없는 언어 별 세부 사항입니다.

이제 언어 별 세부 정보는 ...

나는 class 라는 단어를 사용 했지만 javascript에는 Class라는 개념이 없습니다. 그것은 객체를 가지고 있으며 그게 전부입니다 (단순 유형 제외). Javascript는 프로토 타입 상속을 사용합니다. 즉, 객체와 해당 객체에 대한 메서드를 효율적으로 정의하는 방법이 있습니다 (이것은 다른 질문에 대한 주제입니다. 이미 답변이 있으므로 SO를 검색 할 수 있습니다.)

위의 예를 살펴보면 A, B, C가 있습니다.

상속을 위해 당신은

// define an object (which can be viewed as a "class")
function A(){}

// define some functionality
A.prototype.someMethod = function(){}

C가 A를 확장하도록하려면 다음을 수행합니다.

C.prototype = new A();
C.prototype.constructor = A;

이제 C의 모든 인스턴스는 메서드를 가질 것입니다. C의 someMethod모든 인스턴스는 "isA"A입니다.

자바 스크립트에는 다중 상속 *이 없으므로 (나중에 자세히 설명) C가 A와 B를 모두 확장 할 수는 없습니다. 그러나 컴포지션을 사용하여 기능을 제공 할 수 있습니다. 실제로 이것은 상속보다 구성이 선호되는 이유 중 하나입니다. 기능 결합에는 제한이 없습니다 (그러나 이것이 유일한 이유는 아닙니다).

function C(){
   this.a = new A();
   this.b = new B();
}

// someMethod on C invokes the someMethod on B.
C.someMethod = function(){
    this.a.someMethod()
}

따라서 상속과 구성에 대한 간단한 예가 있습니다. 그러나 이것이 이야기의 끝이 아닙니다. 이전에 Javascript는 다중 상속을 지원하지 않는다고 말했고, 어떤 의미에서는 그렇지 않습니다. 여러 객체의 프로토 타입에서 객체의 프로토 타입을 기반으로 할 수 없기 때문입니다. 즉 당신은 할 수 없습니다

C.prototype = new B();
C.prototype.constructor = B;
C.prototype.constructor = A;

세 번째 줄을하자마자 두 번째 줄을 풀기 때문입니다. 이것은 instanceof운영자 에게 의미가 있습니다.

그러나 객체의 생성자를 두 번 재정의 할 수 없기 때문에 원하는 메서드를 객체의 프로토 타입에 추가 할 수 있기 때문에 이것은 실제로 중요하지 않습니다 . 따라서 위의 예를 수행 할 수 없기 때문에 A와 B 모두의 프로토 타입에있는 모든 메서드를 포함하여 원하는 모든 것을 C.prototype에 추가 할 수 있습니다 .

많은 프레임 워크가이를 지원하고 쉽게 만듭니다. 저는 Sproutcore 작업을 많이합니다. 그 프레임 워크로 할 수있는

A = {
   method1: function(){}
}

B = {
   method2: function(){}
}

C = SC.Object.extend(A, B, {
   method3: function(){}
}

여기에서는 객체 리터럴 A및의 기능을 정의한 B다음에 둘 다의 기능을 추가 C했으므로 C의 모든 인스턴스에는 메서드 1, 2 및 3이 있습니다.이 특정 경우에는 extend메서드 (프레임 워크에서 제공)가 모든 무거운 작업을 수행합니다. 객체의 프로토 타입을 설정하는 것입니다.

편집-귀하의 의견에서 "구성을 사용하는 경우 기본 개체가 구성되는 개체의 범위에 대해 주 개체의 범위를 어떻게 조정합니까?"라는 좋은 질문을 던집니다.

여러 가지 방법이 있습니다. 첫 번째는 단순히 인수를 전달하는 것입니다. 그래서

C.someMethod = function(){
    this.a.someMethod(arg1, arg2...);
}

여기서는 범위를 엉망으로 만드는 것이 아니라 단순히 인수를 전달하는 것입니다. 이것은 간단하고 실행 가능한 접근 방식입니다. (인수는 this무엇이든간에 전달되거나 전달 될 수 있습니다 ...)

이를 수행하는 또 다른 방법은 기본적으로 함수의 범위를 설정할 수있는 자바 스크립트 call(또는 apply) 메소드 를 사용 하는 것입니다.

C.someMethod = function(){
    this.a.someMethod.call(this, arg1, arg2...);
}

좀 더 명확하게 말하면 다음은 동일합니다.

C.someMethod = function(){
    var someMethodOnA = this.a.someMethod;
    someMethodOnA.call(this, arg1, arg2...);
}

자바 스크립트에서 함수는 객체이므로 변수에 할당 할 수 있습니다.

call여기의 범위로 설정되는 호출 someMethodOnAthisC.의 인스턴스이며


... 누군가가 위 코드를 사용하여 구성 및 집계를 보여주는 예제를 제공 할 수 있습니까?

언뜻보기에 제공된 예제는 JavaScript로 구성을 보여주기위한 최선의 선택이 아닌 것 같습니다. prototype의 특성 Stock생성자 함수는 여전히 두 방법 모두를위한 이상적인 장소 유지 total하고 list있는 재고 객체의 자신의 특성을 모두 DO 액세스.

할 수있는 것은 생성자 프로토 타입에서 이러한 메서드의 구현을 분리하고 정확히 거기에 다시 제공하는 것입니다. 그러나 추가적인 코드 재사용 형태로 Mixins ...

예:

var Iterable_listAllKeys = (function () {

    var
        Mixin,

        object_keys = Object.keys,

        listAllKeys = function () {
            return object_keys(this).join(", ");
        }
    ;

    Mixin = function () {
        this.list = listAllKeys;
    };

    return Mixin;

}());


var Iterable_computeTotal = (function (global) {

  var
      Mixin,

      currencyFlag,

      object_keys = global.Object.keys,
      parse_float = global.parseFloat,

      aggregateNumberValue = function (collector, key) {
          collector.value = (
              collector.value
              + parse_float(collector.target[key], 10)
          );
          return collector;
      },
      computeTotal = function () {
          return [

              currencyFlag,
              object_keys(this)
                  .reduce(aggregateNumberValue, {value: 0, target: this})
                  .value
                  .toFixed(2)

          ].join(" ");
      }
    ;

    Mixin = function (config) {
        currencyFlag = (config && config.currencyFlag) || "";

        this.total = computeTotal;
    };

    return Mixin;

}(this));


var Stock = (function () {

  var
      Stock,

      object_keys = Object.keys,

      createKeyValueForTarget = function (collector, key) {
          collector.target[key] = collector.config[key];
          return collector;
      },
      createStock = function (config) { // Factory
          return (new Stock(config));
      },
      isStock = function (type) {
          return (type instanceof Stock);
      }
  ;

  Stock = function (config) { // Constructor
      var stock = this;
      object_keys(config).reduce(createKeyValueForTarget, {

          config: config,
          target: stock
      });
      return stock;
  };

  /**
   *  composition:
   *  - apply both mixins to the constructor's prototype
   *  - by delegating them explicitly via [call].
   */
  Iterable_listAllKeys.call(Stock.prototype);
  Iterable_computeTotal.call(Stock.prototype, {currencyFlag: "$"});

  /**
   *  [[Stock]] factory module
   */
  return {
      isStock : isStock,
      create  : createStock
  };

}());


var stock = Stock.create({MSFT: 25.96, YHOO: 16.13, AMZN: 173.10});

/**
 *  both methods are available due to JavaScript's
 *  - prototypal delegation automatism that covers inheritance.
 */
console.log(stock.list());
console.log(stock.total());

console.log(stock);
console.dir(stock);

온라인에서 구성과 상속에 대한 많은 정보가 있지만 JavaScript로 적절한 예를 찾지 못했습니다. ...

온라인에서 JavaScript의 구성에 대한 많은 정보를 찾지 못했고 다른 언어에서만 찾았습니다. ...

검색 쿼리가 충분히 구체적이지 않았을 수도 있지만 2012 년에도 "JavaScript Mixin 구성"을 검색해도 그렇게 나쁜 방향은 아니 었어 야합니다.

... OOP의 많은 상황에서 구성이 선호된다면 JavaScript를 사용하는 대부분의 사람들은 왜 프로토 타입과 상속만을 사용하는 것처럼 보입니까?

그들 대부분이 사용하기 때문에 그들이 힘들었던 것 및 / 또는 그들이 좋아하는 것을 사용합니다. 위임 기반 언어로서 JavaScript에 대한 더 많은 지식이 확산되어야하며이를 통해 얻을 수있는 것은 무엇일까요?

부록:

이것은 최근에 업데이트되어 도움이되는 관련 스레드입니다.


일반 JavaScript (ES5)를 사용하여 "객체 구성"방식으로 코드를 다시 작성하는 방법을 보여줄 수 있다고 생각합니다. 객체 인스턴스를 만들기 위해 생성자 함수 대신 팩토리 함수를 사용 하므로 new키워드가 필요 하지 않습니다. 이렇게하면 클래식 / 의사-클래식 / 프로토 티팔 상속보다 객체 증가 (구성)를 선호 할 수 있으므로 Object.create함수가 호출 되지 않습니다 .

결과 객체는 멋진 평면 구성 객체입니다.

/*
 * Factory function for creating "abstract stock" object. 
 */
var AbstractStock = function (options) {

  /**
   * Private properties :)
   * @see http://javascript.crockford.com/private.html
   */
  var companyList = [],
      priceTotal = 0;

  for (var companyName in options) {

    if (options.hasOwnProperty(companyName)) {
      companyList.push(companyName);
      priceTotal = priceTotal + options[companyName];
    }
  }

  return {
    /**
     * Privileged methods; methods that use private properties by using closure. ;)
     * @see http://javascript.crockford.com/private.html
     */
    getCompanyList: function () {
      return companyList;
    },
    getPriceTotal: function () {
      return priceTotal;
    },
    /*
     * Abstract methods
     */
    list: function () {
      throw new Error('list() method not implemented.');
    },
    total: function () {
      throw new Error('total() method not implemented.');
    }
  };
};

/*
 * Factory function for creating "stock" object.
 * Here, since the stock object is composed from abstract stock
 * object, you can make use of properties/methods exposed by the 
 * abstract stock object.
 */
var Stock = compose(AbstractStock, function (options) {

  return {
    /*
     * More concrete methods
     */
    list: function () {
      console.log(this.getCompanyList().toString());
    },
    total: function () {
      console.log('$' + this.getPriceTotal());
    }
  };
});

// Create an instance of stock object. No `new`! (!)
var portofolio = Stock({MSFT: 25.96, YHOO: 16.13, AMZN: 173.10});
portofolio.list(); // MSFT,YHOO,AMZN
portofolio.total(); // $215.19

/*
 * No deep level of prototypal (or whatsoever) inheritance hierarchy;
 * just a flat object inherited directly from the `Object` prototype.
 * "What could be more object-oriented than that?" –Douglas Crockford
 */ 
console.log(portofolio); 



/*
 * Here is the magic potion:
 * Create a composed factory function for creating a composed object.
 * Factory that creates more abstract object should come first. 
 */
function compose(factory0, factoryN) {
  var factories = arguments;

  /*
   * Note that the `options` passed earlier to the composed factory
   * will be passed to each factory when creating object.
   */
  return function (options) {

    // Collect objects after creating them from each factory.
    var objects = [].map.call(factories, function(factory) {
      return factory(options);
    });

    // ...and then, compose the objects.
    return Object.assign.apply(this, objects);
  };
};

여기 바이올린 .

ReferenceURL : https://stackoverflow.com/questions/8696695/composition-inheritance-and-aggregation-in-javascript

반응형