developer tip

Kyle Simpson의 OLOO 패턴과 프로토 타입 디자인 패턴

copycodes 2020. 8. 16. 20:59
반응형

Kyle Simpson의 OLOO 패턴과 프로토 타입 디자인 패턴


Kyle Simpson의 "OLOO (다른 개체에 연결되는 개체) 패턴"이 프로토 타입 디자인 패턴과 다른 점이 있습니까? "연결"(프로토 타입의 동작)을 구체적으로 나타내는 무언가로 만든 것 외에 여기에서 "복사"가 일어나지 않는다는 것을 명확히하는 것 (클래스의 동작) 외에 그의 패턴은 정확히 무엇을 소개합니까?

다음 은 그의 저서 "You Do n't Know JS : this & Object Prototypes" 에있는 Kyle의 패턴 예입니다 .

var Foo = {
    init: function(who) {
        this.me = who;
    },
    identify: function() {
        return "I am " + this.me;
    }
};

var Bar = Object.create(Foo);

Bar.speak = function() {
    alert("Hello, " + this.identify() + ".");
};

var b1 = Object.create(Bar);
b1.init("b1");
var b2 = Object.create(Bar);
b2.init("b2");

b1.speak(); // alerts: "Hello, I am b1."
b2.speak(); // alerts: "Hello, I am b2."

그의 패턴은 정확히 무엇을 소개합니까?

OLOO는 링크를 얻기 위해 다른 (IMO 혼란스러운) 의미 체계에 계층화 할 필요없이 프로토 타입 체인을있는 그대로 수용합니다.

따라서이 두 스 니펫은 정확히 동일한 결과를 갖지만 다른 방식으로 도달합니다.

생성자 양식 :

function Foo() {}
Foo.prototype.y = 11;

function Bar() {}
Bar.prototype = Object.create(Foo.prototype);
Bar.prototype.z = 31;

var x = new Bar();
x.y + x.z;  // 42

OLOO 양식 :

var FooObj = { y: 11 };

var BarObj = Object.create(FooObj);
BarObj.z = 31;

var x = Object.create(BarObj);
x.y + x.z;  // 42

두 스 니펫 모두에서 x객체는 [[Prototype]]객체 ( Bar.prototype또는 BarObj)에 연결되고 차례로 세 번째 객체 ( Foo.prototype또는 FooObj)에 연결됩니다 .

스 니펫간에 관계 및 위임은 동일합니다. 스 니펫간에 메모리 사용량은 동일합니다. 많은 "자식"(일명 x1through 와 같은 많은 객체)을 생성하는 기능 x1000은 스 니펫간에 동일합니다. 위임 ( x.yx.z) 의 성능은 스 니펫간에 동일합니다. OLOO에서는 개체 생성 성능 느리지 만 성능 저하가 실제로 문제가되지 않음 보여주는 온 전성 검사입니다 .

OLOO가 제공하는 것은 생성자 / new메커니즘을 통해 간접적으로 연결하는 것보다 개체를 표현하고 직접 연결하는 것이 훨씬 더 간단하다는 것 입니다. 후자는 클래스에 관한 척하지만 실제로는 위임을 표현하기위한 끔찍한 구문 일뿐입니다 ( 참고 : ES6 class구문도 마찬가지입니다 !).

OLOO는 중개인을 잘라 내고 있습니다.

다음 OLOO와의 또 다른 비교 입니다 class.


나는 Kyle의 책을 읽었고, 특히 this바인딩 방법에 대한 세부 사항이 정말 유익하다는 것을 알았습니다 .

장점 :

저에게는 OLOO의 몇 가지 큰 장점이 있습니다.

1. 단순성

OLOO Object.create()[[prototype]]다른 개체에 연결된 새 개체를 만드는 데 의존 합니다. 함수에 prototype속성 이 있다는 것을 이해 하거나 수정으로 인한 잠재적 관련 함정에 대해 걱정할 필요가 없습니다.

2. 깔끔한 구문

이것은 논쟁의 여지가 있지만 OLOO 구문이 (많은 경우) '표준'자바 스크립트 접근 방식보다 더 깔끔하고 간결하다고 생각합니다 super.

단점 :

의심스러운 디자인 (실제로 위의 2 번 항목에 기여하는 디자인)이 있다고 생각합니다. 이는 섀도 잉과 관련이 있습니다.

행동 위임에서 우리는 [[Prototype]]체인의 다른 수준에서 동일한 이름을 가능한 한 피합니다 .

이것의이면에있는 아이디어는 객체가 자신의 더 구체적인 기능을 가지고 있으며 내부적으로 체인 아래의 기능에 위임한다는 것입니다. 예를 들어, resource객체 save()의 JSON 버전을 서버로 보내는 함수 가있는 객체가 있을 수 있지만 , 먼저 서버로 보내서는 안되는 속성을 제거 clientResource하는 stripAndSave()함수 가있는 객체 가있을 수 있습니다. .

이 잠재적 인 문제점은 다음과 같습니다 다른 사람이 함께 제공하고 만들하기로 결정하면 specialResource개체, 전체 프로토 타입 체인을 충분히 인식하지, 그들은 합리적 *라는 속성에서 마지막으로 저장을 위해 타임 스탬프를 저장하도록 결정할 수 있습니다 save기본 그림자, save()기능에 대한을 resource개체 프로토 타입 체인 아래 두 개의 링크 :

var resource = {
  save: function () { 
    console.log('Saving');
  }
};

var clientResource = Object.create(resource);

clientResource.stripAndSave = function () {
  // Do something else, then delegate
  console.log('Stripping unwanted properties');
  this.save();
};

var specialResource = Object.create( clientResource );

specialResource.timeStampedSave = function () {
  // Set the timestamp of the last save
  this.save = Date.now();
  this.stripAndSave();
};

a = Object.create(clientResource);
b = Object.create(specialResource);

a.stripAndSave();    // "Stripping unwanted properties" & "Saving".
b.timeStampedSave(); // Error!

이것은 특별히 고안된 예이지만 요점은 특별히 다른 속성을 숨기지 않으면 어색한 상황과 동의어 사전을 많이 사용할 수 있다는 것입니다!

아마도 이것에 대한 더 나은 예시는 init메소드 일 것입니다. 특히 OOLO가 생성자 유형 함수를 회피하기 때문에 가슴이 벅차 오릅니다. 모든 관련 개체에는 이러한 기능이 필요할 수 있으므로 적절하게 이름을 지정하는 것은 지루한 작업이 될 수 있으며 고유성으로 인해 사용할 항목을 기억하기 어려울 수 있습니다.

* 사실 특별히 합리적이지는 않습니다 ( lastSaved훨씬 더 좋겠지 만 단지 예일뿐입니다.)


"You Do n't Know JS : this & Object Prototypes"의 토론과 OLOO의 프레젠테이션은 생각을 자극하며 책을 통해 많은 것을 배웠습니다. OLOO 패턴의 장점은 다른 답변에서 잘 설명되어 있습니다. 그러나 나는 그것에 대해 다음과 같은 애완 동물 불만이 있습니다 (또는 효과적으로 적용하지 못하는 것을 놓치고 있습니다).

1

클래식 패턴에서 "클래스"가 다른 "클래스"를 "상속"할 때 두 함수는 유사한 구문 ( "함수 선언"또는 "함수 문" ) 으로 선언 될 수 있습니다 .

function Point(x,y) {
    this.x = x;
    this.y = y;
};

function Point3D(x,y,z) {
    Point.call(this, x,y);
    this.z = z;
};

Point3D.prototype = Object.create(Point.prototype);

반대로 OLOO 패턴에서는 기본 및 파생 개체를 정의하는 데 사용되는 다양한 구문 형식이 있습니다.

var Point = {
    init  : function(x,y) {
        this.x = x;
        this.y = y;
    }
};


var Point3D = Object.create(Point);
Point3D.init = function(x,y,z) {
    Point.init.call(this, x, y);
    this.z = z;
};

위의 예에서 볼 수 있듯이 기본 개체는 개체 리터럴 표기법을 사용하여 정의 할 수 있지만 동일한 표기법은 파생 개체에 사용할 수 없습니다. 이 비대칭은 나를 괴롭 힙니다.

2

OLOO 패턴에서 개체를 만드는 것은 두 단계입니다.

  1. 요구 Object.create
  2. 객체를 초기화하기 위해 표준이 아닌 사용자 정의 메서드를 호출합니다 (객체마다 다를 수 있으므로 기억해야합니다).

     var p2a = Object.create(Point);
    
     p2a.init(1,1);
    

반대로 프로토 타입 패턴에서는 표준 연산자를 사용합니다 new.

var p2a = new Point(1,1);

클래식 패턴에서는 "클래스"함수에 직접 할당하여 "인스턴트"에 직접 적용되지 않는 "정적"유틸리티 함수를 만들 수 있습니다 ( .prototype). square를 들어 아래 코드의 기능 과 같습니다.

Point.square = function(x) {return x*x;};

Point.prototype.length = function() {
    return Math.sqrt(Point.square(this.x)+Point.square(this.y));
};

반대로 OLOO 패턴에서는 객체 인스턴스에서도 [[prototype]] 체인을 통해 "정적"함수를 사용할 수 있습니다.

var Point = {
    init  : function(x,y) {
        this.x = x;
        this.y = y;
    },
    square: function(x) {return x*x;},
    length: function() {return Math.sqrt(Point.square(this.x)+Point.square(this.y));}
};

"각 obj가 서로에 종속되게 만들려고했습니다."

Kyle이 설명하는 것처럼 두 개체가 [[Prototype]]연결될 실제로 서로 의존하지 않습니다. 대신 그들은 개별 개체입니다. [[Prototype]]언제든지 변경할 수 있는 연결을 사용하여 한 개체를 다른 개체에 연결합니다 . OLOO 스타일로 생성 된 두 개의 [[Prototype]]연결된 객체를 서로 의존하는 것으로 간주한다면 호출을 통해 생성 된 객체에 대해서도 동일하게 생각해야 constructor합니다.

var foo= {},
    bar= Object.create(foo),
    baz= Object.create(bar);


console.log(Object.getPrototypeOf(foo)) //Object.prototype

console.log(Object.getPrototypeOf(bar)) //foo

console.log(Object.getPrototypeOf(baz)) //bar

이제 당신은 어떻게 생각하십니까 잠시 생각 foo barbaz각 - 기타에 의존로?

이제이 constructor스타일 코드를 똑같이 해봅시다 .

function Foo() {}

function Bar() {}

function Baz() {}

Bar.prototype= Object.create(Foo);
Baz.prototype= Object.create(Bar);

var foo= new Foo(),
    bar= new Bar().
    baz= new Baz();

console.log(Object.getPrototypeOf(foo)) //Foo.prototype
console.log(Object.getPrototypeOf(Foo.prototype)) //Object.prototype

console.log(Object.getPrototypeOf(bar)) //Bar.prototype
console.log(Object.getPrototypeOf(Bar.prototype)) //Foo.prototype

console.log(Object.getPrototypeOf(baz)) //Baz.prototype
console.log(Object.getPrototypeOf(Baz.prototype)) //Bar.prototype

유일한 차이점 B는 / 후자와 전자의 코드 승하면 후자 한 점이다 foo, bar, bazbbjects 그들의 임의의 물체들 각각-서로 연결된 constructor함수 ( Foo.prototype, Bar.prototype, Baz.prototype)하지만 전 하나 (에 OLOO가 직접 링크되는 스타일). 당신은 그냥 연결하고 두 가지 방법 foo, bar, baz직접 전 하나에 간접적으로 후자에 서로. 그러나 두 경우 모두 개체는 인스턴스화되면 다른 클래스에서 상속 할 수없는 클래스의 인스턴스와 같지 않기 때문에 서로 독립적입니다. 객체가 위임해야하는 객체도 언제든지 변경할 수 있습니다.

var anotherObj= {};
Object.setPrototypeOf(foo, anotherObj);

그래서 그들은 모두 서로 독립적입니다.

" OLOO각 물체가 서로에 대해 전혀 모르는 문제가 해결 되기를 바랐습니다 ."

네 가능합니다-

Tech유틸리티 객체로 사용합시다-

 var Tech= {
     tag: "technology",
     setName= function(name) {
              this.name= name;
}
}

당신이 연결 원하는대로 많은 개체로 만들 Tech-

var html= Object.create(Tech),
     css= Object.create(Tech),
     js= Object.create(Tech);

Some checking (avoiding console.log)- 

    html.isPrototypeOf(css); //false
    html.isPrototypeOf(js); //false

    css.isPrototypeOf(html); //false
    css.isPrototypeOf(js); //false

    js.isPrototypeOf(html); //false
    js.isPrototypwOf(css); //false

    Tech.isPrototypeOf(html); //true
    Tech.isPrototypeOf(css); //true
    Tech.isPrototypeOf(js); //true

당신은 생각하십니까 html, css, js객체가 각 - 기타에 연결되어 있습니까? 아니요, 그렇지 않습니다. 이제 우리가 constructor함수로 어떻게 할 수 있었는지 봅시다.

function Tech() { }

Tech.prototype.tag= "technology";

Tech.prototype.setName=  function(name) {
              this.name= name;
}

당신이 연결 원하는대로 많은 개체로 만들 Tech.proptotype-

var html= new Tech(),
     css= new Tech(),
      js= new Tech();

일부 검사 (console.log 방지)-

html.isPrototypeOf(css); //false
html.isPrototypeOf(js); //false

css.isPrototypeOf(html); //false
css.isPrototypeOf(js); //false

js.isPrototypeOf(html); //false
js.isPrototypeOf(css); //false

Tech.prototype.isPrototypeOf(html); //true
Tech.prototype.isPrototypeOf(css); //true
Tech.prototype.isPrototypeOf(js); //true

어떻게 이런 생각하십니까 constructor스타일 개체 ( html, css, js) 객체는 다를 OLOO스타일 코드? 사실 그들은 같은 목적을 가지고 있습니다. OLOO-style 에서는 하나의 객체가 위임 Tech(명시 적으로 위임이 설정 됨)하는 반면, constructor스타일에서는 하나의 객체가 위임 Tech.prototype(암시 적으로 위임 됨)에 위임됩니다 . 궁극적 OLOO으로 constructor-style을 사용하여 간접적으로 -style을 사용하여 서로 연결되지 않은 세 개체를 하나의 개체에 연결 합니다.

"그대로 ObjB는 ObjA .. Object.create (ObjB) 등에서 생성되어야합니다."

아니요, ObjB여기는 어떤 클래스의 인스턴스 (고전 기반 언어)와 다릅니다 ObjA. 생성 시점 객체 객체에 위임 된 것처럼 말입니다objBObjA . 생성자를 사용했다면 .prototypes 를 사용하여 간접적으로도 동일한 '커플 링'을 수행했을 것 입니다.


트윗 담아 가기

아마도 우리는 이와 같은 것을 할 수 있습니다.

    const Point = {

        statics(m) { if (this !== Point) { throw Error(m); }},

        create (x, y) {
            this.statics();
            var P = Object.create(Point);
            P.init(x, y);
            return P;
        },

        init(x=0, y=0) {
            this.x = x;
            this.y = y;
        }
    };


    const Point3D = {

        __proto__: Point,

        statics(m) { if (this !== Point3D) { throw Error(m); }},

        create (x, y, z) {
            this.statics();
            var P = Object.create(Point3D);
            P.init(x, y, z);
            return P;
        },

        init (x=0, y=0, z=0) {
            super.init(x, y);
            this.z = z;
        }
    }; 

물론, Point2D 개체의 프로토 타입에 연결되는 Point3D 개체를 만드는 것은 다소 어리석은 일이지만 요점을 벗어난 것입니다 (예를 들어 일관성을 유지하고 싶었습니다). 어쨌든, 불만이있는 한 :

  1. 비대칭은 ES6의 Object.setPrototypeOf 또는 __proto__ = ...내가 사용 하는 것에 대해 더 눈살을 찌푸리는 것으로 수정할 수 있습니다 . 에서 볼 수 있듯이 이제 일반 객체 에도 super사용할 수 있습니다 Point3D.init(). 또 다른 방법은 다음과 같이하는 것입니다.

    const Point3D = Object.assign(Object.create(Point), {  
        ...  
    }   
    

    구문이 특히 마음에 들지는 않지만.


  1. 우리는 항상 래핑 p = Object.create(Point)한 다음 p.init()생성자로 만들 수 있습니다. 예 : Point.create(x,y). 위의 코드를 사용하여 Point3D다음과 같은 방식으로 "인스턴스"를 만들 수 있습니다 .

    var b = Point3D.create(1,2,3);
    console.log(b);                         // { x:1, y:2, z:3 }
    console.log(Point.isPrototypeOf(b));    // true
    console.log(Point3D.isPrototypeOf(b))   // true
    

  1. OLOO에서 정적 메서드를 에뮬레이트하기 위해 방금이 해킹을 생각해 냈습니다. 내가 좋아하는지 아닌지 잘 모르겠습니다. "정적"메서드의 맨 위에서 특수 속성을 호출해야합니다. 예를 들어, Point.create()메서드를 정적으로 만들었습니다 .

        var p = Point.create(1,2);
        var q = p.create(4,1);          // Error!  
    

또는 ES6 기호 를 사용하여 Javascript 기본 클래스를 안전하게 확장 할 수 있습니다. 따라서 일부 코드를 저장하고 Object.prototype에 대한 특수 속성을 정의 할 수 있습니다. 예를 들면

    const extendedJS = {};  

    ( function(extension) {

        const statics = Symbol('static');

        Object.defineProperty(Object.prototype, statics, {
            writable: true,
            enumerable: false,
            configurable: true,
            value(obj, message) {
                if (this !== obj)
                    throw Error(message);
            }
        });

        Object.assign(extension, {statics});

    })(extendedJS);


    const Point = {
        create (x, y) {
            this[extendedJS.statics](Point);
            ...


@james emanon-그래서, 당신은 다중 상속을 언급하고 있습니다 ( "You Do n't Know JS : this & Object Prototypes"책의 75 페이지에서 논의 됨). 그리고 그 메커니즘은 예를 들어 밑줄의 "확장"기능에서 찾을 수 있습니다. 귀하의 예에서 언급 한 물건의 이름은 사과, 오렌지 및 사탕을 약간 섞은 것이지만 뒤에있는 요점을 이해합니다. 내 경험에 따르면 이것은 OOLO 버전이 될 것입니다.

var ObjA = {
  setA: function(a) {
    this.a = a;
  },
  outputA: function() {
    console.log("Invoking outputA - A: ", this.a);
  }
};

// 'ObjB' links/delegates to 'ObjA'
var ObjB = Object.create( ObjA );

ObjB.setB = function(b) {
   this.b = b;
}

ObjB.setA_B = function(a, b) {
    this.setA( a ); // This is obvious. 'setA' is not found in 'ObjB' so by prototype chain it's found in 'ObjA'
    this.setB( b );
    console.log("Invoking setA_B - A: ", this.a, " B: ", this.b);
};

// 'ObjC' links/delegates to 'ObjB'
var ObjC = Object.create( ObjB );

ObjC.setC = function(c) {
    this.c = c;  
};

ObjC.setA_C = function(a, c) {
    this.setA( a ); // Invoking 'setA' that is clearly not in ObjC shows that prototype chaining goes through ObjB all the way to the ObjA
    this.setC( c );
    console.log("Invoking setA_C - A: ", this.a, " C: ", this.c);
};

ObjC.setA_B_C = function(a, b, c){
    this.setA( a ); // Invoking 'setA' that is clearly not in ObjC nor ObjB shows that prototype chaining got all the way to the ObjA
    this.setB( b );
    this.setC( c );
    console.log("Invoking setA_B_C - A: ", this.a, " B: ", this.b, " C: ", this.c);
};

ObjA.setA("A1");
ObjA.outputA(); // Invoking outputA - A:  A1

ObjB.setA_B("A2", "B1"); // Invoking setA_B - A:  A2  B:  B1

ObjC.setA_C("A3", "C1"); // Invoking setA_C - A:  A3  C:  C1
ObjC.setA_B_C("A4", "B2", "C1"); // Invoking setA_B_C - A:  A4  B:  B2  C:  C1

간단한 예이지만 표시된 요점은 단순한 구조 / 형성으로 개체를 연결하고 여러 개체의 메서드와 속성을 사용할 수 있다는 것입니다. 우리는 클래스 / "속성 복사"접근 방식과 동일한 것을 달성합니다. Kyle에 의해 요약 됨 (114 페이지, "this & Object Prototypes") :

즉, JavaScript에서 활용할 수있는 기능에 중요한 요소 인 실제 메커니즘은 다른 객체에 연결된 객체에 관한 것 입니다.

더 자연스러운 방법은 전체 체인을 모델링하는 것보다 한 장소 / 함수 호출에 모든 "부모"(주의 :)) 객체를 명시하는 것임을 이해합니다.

그것이 요구하는 것은 그것에 따라 우리 응용 프로그램에서 사고와 모델링 문제의 변화입니다. 나는 또한 그것에 익숙해지고 있습니다. 도움이되기를 바라며 Kyle 자신의 최종 평결이 좋을 것입니다. :)


@Marcus, 당신과 마찬가지로 나는 OLOO에 열중하고 첫 번째 요점에서 설명한 비대칭도 싫어했습니다. 저는 대칭을 되찾기 위해 추상화를 가지고 놀았습니다. link()대신 사용되는 함수를 만들 수 있습니다 Object.create(). 사용하면 코드가 다음과 같이 보일 수 있습니다.

var Point = {
    init  : function(x,y) {
        this.x = x;
        this.y = y;
    }
};


var Point3D = link(Point, {
    init: function(x,y,z) {
        Point.init.call(this, x, y);
        this.z = z;
    }
});

Object.create()전달할 수있는 두 번째 매개 변수가 있음을 기억하십시오 . 다음은 두 번째 매개 변수를 활용하는 링크 함수입니다. 또한 약간의 사용자 지정 구성이 가능합니다.

function link(delegate, props, propsConfig) {
  props = props || {};
  propsConfig = propsConfig || {};

  var obj = {};
  Object.keys(props).forEach(function (key) {
    obj[key] = {
      value: props[key],
      enumerable: propsConfig.isEnumerable || true,
      writable: propsConfig.isWritable || true,
      configurable: propsConfig.isConfigurable || true
    };
  });

  return Object.create(delegate, obj);
}

물론 @Kyle은 init()Point3D 객체 함수를 섀도 잉하는 것을 보증하지 않을 것이라고 생각 합니다. ;-)


"두"개 이상의 객체를 OLOO하는 방법이 있습니까 .. 모든 예제는 기반 예제로 구성됩니다 (OP의 예제 참조). 다음 개체가 있다고 가정 해 봅시다. '기타'3 개의 속성을 가진 "네 번째"개체를 어떻게 만들 수 있습니까? 알라 ...

var Button = {
     init: function(name, cost) {
       this.buttonName = name;
       this.buttonCost = cost;
     }
}

var Shoe = {
     speed: 100
}

var Bike = {
     range: '4 miles'
}

이러한 개체는 임의적이며 모든 종류의 동작을 포함 할 수 있습니다. 그러나 요점은 'n'개의 객체가 있고 새 객체에는 세 가지 모두에서 무언가가 필요하다는 것입니다.

주어진 예 ala 대신 :

var newObj = Object.create(oneSingularObject);
    newObj.whatever..

그러나 우리의 newObject = (단추, 자전거, 신발) ......

OLOO에서이 작업을 수행하는 패턴은 무엇입니까?

참고 URL : https://stackoverflow.com/questions/29788181/kyle-simpsons-oloo-pattern-vs-prototype-design-pattern

반응형