The Mixin Pattern
C ++ 및 Lisp와 같은 전통적인 프로그래밍 언어에서 Mixins는 함수 재사용을 위해 하위 클래스 또는 하위 클래스 그룹에 쉽게 상속 될 수있는 기능을 제공하는 클래스입니다.
Sub-classing
하위 분류에 익숙하지 않은 개발자를 위해 Mixins 및 Decorators로 다이빙하기 전에 간단한 초보자 입문서를 살펴 보겠습니다.
하위 클래스는 기본 또는 수퍼 클래스 객체의 새 객체에 대한 속성을 상속받는 것을 나타내는 용어입니다. 전통적인 객체 지향 프로그래밍에서 클래스 B는 다른 클래스 A
를 확장 할 수 있습니다. 여기에서 A
를 수퍼 클래스로, B
를 A
의 서브 클래스로 간주합니다. 따라서 B
의 모든 인스턴스는 A
에서 메소드를 상속받습니다. 원래 A
로 정의 된 메서드를 재정의하는 메소드를 포함하여 자체 메서드를 정의합니다.
B
가 재정의 된 A
에서 메소드를 호출해야하는 경우이를 메소드 체인이라고합니다. B
가 생성자 A
(수퍼 클래스)를 호출해야하는 경우이 생성자를 체인이라고합니다.
하위 분류를 증명하기 위해, 우리는 먼저 자신의 새로운 인스턴스를 생성 할 수있는 기본 객체가 필요합니다. 이 개념을 한 사람의 개념으로 모델링 해 봅시다.
var Person = function( firstName, lastName ){
this.firstName = firstName;
this.lastName = lastName;
this.gender = "male";
};
다음에는 기존 Person
객체의 하위 클래스 인 새 클래스 (객체)를 지정하려고합니다. Person
"superclass"의 속성을 상속하면서 한 Person
을 Superhero
와 구별하기 위해 별개의 속성을 추가하려고한다고 가정 해 봅시다. 슈퍼 히어로가 일반인 (예 : 이름, 성별)과 공통된 특징을 공유하므로 하위 분류가 어떻게 적절하게 작동 하는지를 잘 보여줍니다.
// a new instance of Person can then easily be created as follows:
var clark = new Person( "Clark", "Kent" );
// Define a subclass constructor for for "Superhero":
var Superhero = function( firstName, lastName, powers ){
// Invoke the superclass constructor on the new object
// then use .call() to invoke the constructor as a method of
// the object to be initialized.
Person.call( this, firstName, lastName );
// Finally, store their powers, a new array of traits not found in a normal "Person"
this.powers = powers;
};
Superhero.prototype = Object.create( Person.prototype );
var superman = new Superhero( "Clark", "Kent", ["flight","heat-vision"] );
console.log( superman );
// Outputs Person attributes as well as powers
Superhero
생성자는 Person
의 하위 객체를 생성합니다. 이 유형의 객체는 체인의 상위에있는 객체의 속성을 가지며 Person
객체에 기본값을 설정 한 경우 상속 된 값을 해당 객체와 관련된 값으로 재정의 할 수 있습니다.
Mixins
JavaScript에서는 확장을 통해 기능을 수집하는 수단으로 Mixins을 상속받을 수 있습니다. 우리가 정의한 각각의 새로운 객체는 추가 속성을 상속받을 수있는 프로토 타입을 가지고 있습니다. 프로토 타입은 다른 객체 프로토 타입을 상속 할 수 있지만 더 중요한 것은 여러 객체 인스턴스의 속성을 정의 할 수 있습니다. 우리는이 사실을 활용하여 기능 재사용을 촉진 할 수 있습니다.
Mixins은 최소한의 복잡성으로 객체가 기능을 빌려 (또는 상속) 수 있도록합니다. 패턴이 JavaScript 객체 프로토 타입과 잘 작동하기 때문에 하나의 Mixin뿐 아니라 효과적으로 여러 상속을 통해 기능을 공유하는 데 상당히 유연한 방법을 제공합니다.
여러 가지 객체 프로토 타입간에 쉽게 공유 할 수있는 속성 및 메소드를 사용하여 객체로 볼 수 있습니다. 다음과 같이 표준 객체 리터럴에 Mixin을 포함하는 유틸리티 함수를 정의한다고 가정 해보십시오.
var myMixins = {
moveUp: function(){
console.log( "move up" );
},
moveDown: function(){
console.log( "move down" );
},
stop: function(){
console.log( "stop! in the name of love!" );
}
};
그런 다음 Underscore.js _.extend()
메서드와 같은 도우미를 사용하여이 동작을 포함하도록 기존 생성자 함수의 프로토 타입을 쉽게 확장 할 수 있습니다.
// A skeleton carAnimator constructor
function CarAnimator(){
this.moveLeft = function(){
console.log( "move left" );
};
}
// A skeleton personAnimator constructor
function PersonAnimator(){
this.moveRandomly = function(){ /*..*/ };
}
// Extend both constructors with our Mixin
_.extend( CarAnimator.prototype, myMixins );
_.extend( PersonAnimator.prototype, myMixins );
// Create a new instance of carAnimator
var myAnimator = new CarAnimator();
myAnimator.moveLeft();
myAnimator.moveDown();
myAnimator.stop();
// Outputs:
// move left
// move down
// stop! in the name of love!
우리가 볼 수 있듯이, 이것은 공통된 행동에서 객체 생성자로 쉽게 쉽게 "혼합"할 수있게 해줍니다.
다음 예제에서는 Car와 Mixin의 두 생성자가 있습니다. 우리가하려고하는 것은 Mixin에 정의 된 특정 메소드, 즉 driveForward()
와 driveBackward()
를 상속받을 수 있도록 Car를 보강하는 것입니다. 이번에는 Underscore.js를 사용하지 않을 것입니다.
대신이 예제는 모든 생성자 함수에 대해이 프로세스를 복제 할 필요없이 생성자를 기능을 포함하도록 보강하는 방법을 보여줍니다.
// Define a simple Car constructor
var Car = function ( settings ) {
this.model = settings.model || "no model provided";
this.color = settings.color || "no colour provided";
};
// Mixin
var Mixin = function () {};
Mixin.prototype = {
driveForward: function () {
console.log( "drive forward" );
},
driveBackward: function () {
console.log( "drive backward" );
},
driveSideways: function () {
console.log( "drive sideways" );
}
};
// Extend an existing object with a method from another
function augment( receivingClass, givingClass ) {
// only provide certain methods
if ( arguments[2] ) {
for ( var i = 2, len = arguments.length; i < len; i++ ) {
receivingClass.prototype[arguments[i]] = givingClass.prototype[arguments[i]];
}
}
// provide all methods
else {
for ( var methodName in givingClass.prototype ) {
// check to make sure the receiving class doesn't
// have a method of the same name as the one currently
// being processed
if ( !Object.hasOwnProperty.call(receivingClass.prototype, methodName) ) {
receivingClass.prototype[methodName] = givingClass.prototype[methodName];
}
// Alternatively (check prototype chain as well):
// if ( !receivingClass.prototype[methodName] ) {
// receivingClass.prototype[methodName] = givingClass.prototype[methodName];
// }
}
}
}
// Augment the Car constructor to include "driveForward" and "driveBackward"
augment( Car, Mixin, "driveForward", "driveBackward" );
// Create a new Car
var myCar = new Car({
model: "Ford Escort",
color: "blue"
});
// Test to make sure we now have access to the methods
myCar.driveForward();
myCar.driveBackward();
// Outputs:
// drive forward
// drive backward
// We can also augment Car to include all functions from our mixin
// by not explicitly listing a selection of them
augment( Car, Mixin );
var mySportsCar = new Car({
model: "Porsche",
color: "red"
});
mySportsCar.driveSideways();
// Outputs:
// drive sideways
장점 & 단점
Mixins은 기능적 반복을 감소시키고 시스템에서 기능 재사용을 증가시키는 데 도움이됩니다. 애플리케이션이 객체 인스턴스 전반에서 공유 된 동작을 요구할 가능성이있는 곳에 Mixin에서이 공유 기능을 유지함으로써 중복을 피할 수 있으므로 진정으로 구별되는 시스템의 기능 만 구현하는 데 집중할 수 있습니다.
Mixins의 단점은 좀 더 논쟁의 여지가있다. 일부 개발자는 객체 프로토 타입에 기능을 주입하는 것이 프로토 타입 오염과 함수의 기원에 관한 불확실성의 수준 모두를 초래하기 때문에 나쁜 생각이라고 생각합니다. 대형 시스템에서는 이것이 사실 일 수 있습니다.
나는 강력한 문서가 혼합 된 함수의 출처에 관한 혼란의 양을 최소화하는 데 도움이 될 수 있다고 주장 할 것이다. 그러나 모든 패턴과 마찬가지로 구현 중에주의를 기울이면 괜찮을 것이다.