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
)에 연결됩니다 .
스 니펫간에 관계 및 위임은 동일합니다. 스 니펫간에 메모리 사용량은 동일합니다. 많은 "자식"(일명 x1
through 와 같은 많은 객체)을 생성하는 기능 x1000
은 스 니펫간에 동일합니다. 위임 ( x.y
및 x.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 패턴에서 개체를 만드는 것은 두 단계입니다.
- 요구
Object.create
객체를 초기화하기 위해 표준이 아닌 사용자 정의 메서드를 호출합니다 (객체마다 다를 수 있으므로 기억해야합니다).
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
bar
과 baz
각 - 기타에 의존로?
이제이 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
, baz
bbjects 그들의 임의의 물체들 각각-서로 연결된 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
. 생성 시점 에 객체 가 객체에 위임 된 것처럼 말입니다objB
ObjA
. 생성자를 사용했다면 .prototype
s 를 사용하여 간접적으로도 동일한 '커플 링'을 수행했을 것 입니다.
트윗 담아 가기
아마도 우리는 이와 같은 것을 할 수 있습니다.
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 개체를 만드는 것은 다소 어리석은 일이지만 요점을 벗어난 것입니다 (예를 들어 일관성을 유지하고 싶었습니다). 어쨌든, 불만이있는 한 :
비대칭은 ES6의 Object.setPrototypeOf 또는
__proto__ = ...
내가 사용 하는 것에 대해 더 눈살을 찌푸리는 것으로 수정할 수 있습니다 . 에서 볼 수 있듯이 이제 일반 객체 에도 super 를 사용할 수 있습니다Point3D.init()
. 또 다른 방법은 다음과 같이하는 것입니다.const Point3D = Object.assign(Object.create(Point), { ... }
구문이 특히 마음에 들지는 않지만.
우리는 항상 래핑
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
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
'developer tip' 카테고리의 다른 글
Java에서 XML 직렬화? (0) | 2020.08.16 |
---|---|
NSDefaultRunLoopMode 대 NSRunLoopCommonModes (0) | 2020.08.16 |
Html.RenderPartial로 인해 이상한 과부하 오류가 발생합니까? (0) | 2020.08.16 |
Python 목록 또는 튜플에서 명시 적으로 항목 선택 (0) | 2020.08.16 |
새로운 C / C ++ 표준에 대한 Visual Studio 지원? (0) | 2020.08.16 |