JavaScript – The "Mixin" Design Pattern

כולנו יודעים שב- JavaScript הירושה מתבצעת באמצעות ה- Prototype של הפונקציות, אבל כמה מכם שמעו על Mixins או השתמשו בהם?

Mixins

Mixin הינה מחלקה המכילה סט של מתודות הקשורות לנושא מסויים. לרוב נתייחס ל- Mixin כמחלקה אבסטרקטית ולא נאתחל אותה או נשתמש בה בפני עצמה, אלא נעתיק אותה (או את המתודות שלה) לתוך מחלקה אחרת.

Mixins משמשים בעיקר ל- Code Reuse – אם יש לנו מקומות שונים במערכת שמטעימים פונקציונליות זהה, אנחנו יכולים להוציא אותה ל- Mixin ולכלול אותו בשני המקומות הללו. כותבים את הקוד פעם אחת, משתמשים בו בכל מקום.

ב- JavaScript כפי שאנו יודעים אין מחלקות, אנו נדמה מחלקות באמצעות פונקציות, ו- Mixins נדמה באמצעות אובייקטים.

נתחיל עם דוגמאת קוד בסיסית:

/**
 * Classes
 */
var Person = function(name) {
	this.name = name;
};

var Dog = function(name) {
	this.name = name
};

 
יצרנו 2 מחלקות, הראשונה בשם Person והשנייה בשם Dog. שתיהן מקבלות ב- constructor שלהן את הפרמטר 'name' ושומרות אותו עבור אותו instance.

עכשיו אנחנו רוצים להוסיף ל- 2 המחלקות האלו איזשהי לוגיקה משותפת, אנחנו יודעים לדוג' שגם אנשים וגם כלבים יכולים לזוז – נגיד ללכת ולרוץ, ושניהם יכולים גם לדבר.

עכשיו ניצור Mixins עבור הלוגיקה המשותפת הזאת:

/**
 * Classes
 */
var Person = function(name) {
	this.name = name;
};

var Dog = function(name) {
	this.name = name
};

/**
 * Mixins
 */
var mover = {
	walk: function() {
		console.log(this.name + " is walking.");
	},
	run: function() {
		console.log(this.name + " is running.");
	}
};

var speaker = {
	speak: function() {
		console.log(this.name + " is speaking.");
	}
};

 
עד כאן זה די פשוט, יש לנו 2 מחלקות ו- 2 Mixins.
כל Mixin מייצג איזשהי יכולת פונקציונלית כלשהי – הקוד די ישיר וקל להבנה.

קדימה להטמיע

כפי שאמרנו, אנחנו רוצים להטמיע את היכולות האלו, את ה- Mixins האלו, במחלקות שיצרנו עבור Person ו- Dog. איך אנחנו עושים את זה? בקלות – פשוט מעתיקים את המתודות של ה- Mixins לתוך ה- Prototype של המחלקות שלנו.

– אני יוצא מנקודת הנחה שאתם משתמשים ב- jQuery, שמספקת את הפונקציה $.extend המאפשרת להעתיק אובייקטים. אם לא, ניתן לממש אותה גם בעצמכם כמובן (הקוד בסוף הפוסט).

עכשיו זה נראה ככה:

/**
 * Classes
 */
var Person = function(name) {
	this.name = name;
};

var Dog = function(name) {
	this.name = name
};

/**
 * Mixins
 */
var mover = {
	walk: function() {
		console.log(this.name + " is walking.");
	},
	run: function() {
		console.log(this.name + " is running.");
	}
};

var speaker = {
	speak: function() {
		console.log(this.name + " is speaking.");
	}
};

/**
 * Merge the mixins into the classes
 */
$.extend(Person.prototype, mover, speaker);
$.extend(Dog.prototype, mover, speaker);

 
זה הכל, באמת.

הערות:

1. קוד מלא: http://codepen.io/AdirAmsalem/pen/Fewzk?editors=001
2. מימוש של extend:

/**
 * Merge the contents of two or more objects together into the first object.
 * 
 * @param  {Object} target
 * @param  {Object} source1
 * @param  {Object} sourceN
 *
 * @example
 * 	extend(Person.prototype, speaker); // single source
 * 	extend(Person.prototype, mover, speaker); // multiple sources
 * 
 * @return {Object} the merged object
 */
var extend = function(target) {
	if (!arguments[1]) return target;

	for (var i = 1, l = arguments.length; i < l; i++) {
		var source = arguments[i];

		for (var prop in source) {
			if (!source.hasOwnProperty(prop)) continue;

			target[prop] = source[prop];
		}
	}

	return target;
};