1 /** 2 * @fileOverview 3 * mixing.enchant.js 4 * <p>複数のクラスを混ぜることができるようにし、同じ関数やプロパティを複数のクラスに追加することができるようにするenchant.jsプラグイン. 5 * その上、コピーアンドペーストをしないために、製法({@link enchant.Class.MixingRecipe})で定義された関数やプロパティを 6 * 複数クラスに混ぜることもできるようにする.</p> 7 * <p>これで複数の継承ようなことができるということ</p> 8 * <p>必要なもの:</p><ul> 9 * <li>enchant.js v0.6 以上.</li></ul></p> 10 * 詳細は{@link enchant.Class.mixClasses}, {@link enchant.Class.MixingRecipe.createFromClass}, 11 * {@link enchant.Class.mixClassesFromRecipe}, {@link enchant.Class.MixingRecipe} や 12 * {@link enchant.Class.applyMixingRecipe} 参照してください. 13 * @require enchant.js v0.6+ 14 * 15 * @version 0.1 16 * @author UEI Corporation (Kevin Kratzer) 17 **/ 18 19 if (enchant !== undefined) { 20 (function() { 21 22 /** 23 * @private 24 */ 25 var decorateFunctionFactory = function(srcFunction, currentFunctionName) { 26 return function() { 27 var firstResult, secondResult; 28 firstResult = this._mixing[currentFunctionName].apply(this,arguments); 29 secondResult = srcFunction.apply(this,arguments); 30 if(secondResult) { 31 return secondResult; 32 } 33 if(firstResult) { 34 return firstResult; 35 } 36 }; 37 }; 38 39 /** 40 * @private 41 */ 42 var voidFunction = function(){}; 43 44 /** 45 * @private 46 */ 47 var multipleMixingCombinationFunctionFactory = function(oldFunc,newFunc, key) { 48 return function() { 49 var firstResult = oldFunc.apply(this,arguments); 50 var mixingStore = this._mixing[key]; 51 this._mixing[key] = voidFunction; 52 var secondResult = newFunc.apply(this,arguments); 53 this._mixing[key] = mixingStore; 54 if(secondResult) { 55 return secondResult; 56 } 57 if(firstResult) { 58 return firstResult; 59 } 60 }; 61 }; 62 63 /** 64 * @private 65 */ 66 var createFromPrototypeNonRecursive = function(decorate, override, properties, source, functionOverrideNameList, functionIgnoreNameList, propertyIgnoreNameList) { 67 for(var key in source) { 68 if(source.hasOwnProperty(key)) { 69 var descriptor = Object.getOwnPropertyDescriptor(source, key); 70 if(descriptor.value && typeof(descriptor.value) === 'function') { 71 if((!functionIgnoreNameList || functionIgnoreNameList.indexOf(key) === -1) && key !== 'constructor') { 72 if(!functionOverrideNameList || functionOverrideNameList.indexOf(key) === -1) { 73 decorate[key] = (decorateFunctionFactory(source[key],key)); 74 } else { 75 override[key] = source[key]; 76 } 77 } 78 } else { 79 if(!propertyIgnoreNameList || propertyIgnoreNameList.indexOf(key) === -1 && key !== '_mixing') { 80 properties[key] = descriptor; 81 } 82 } 83 } 84 } 85 }; 86 87 /** 88 * @private 89 */ 90 var createFromPrototype = function(decorate,override,properties,source,onlyOwnProperties, functionOverrideNameList, functionIgnoreNameList, propertyIgnoreNameList) { 91 if(!onlyOwnProperties && source instanceof Object) { 92 createFromPrototype(decorate,override,properties,Object.getPrototypeOf(source),onlyOwnProperties, functionOverrideNameList, functionIgnoreNameList, propertyIgnoreNameList); 93 } 94 createFromPrototypeNonRecursive(decorate,override,properties,source, functionOverrideNameList, functionIgnoreNameList, propertyIgnoreNameList); 95 }; 96 97 /** 98 * @private 99 */ 100 var getFunctionParams = function(methodString) { 101 if(typeof(methodString) !== 'string') { 102 methodString = methodString.toString(); 103 } 104 return methodString.substring(methodString.indexOf('(')+1,methodString.indexOf(')')).replace(/\s+/,'').split(','); 105 }; 106 107 /*Public Interface */ 108 /** 109 * @scope enchant.Class.MixingRecipe.prototype 110 */ 111 enchant.Class.MixingRecipe = enchant.Class.create({ 112 /** 113 * 混ぜる中、どういう風に関数やプロパティが追加されるか設定する新たなMixingRecipeを生成する. 114 * クラスからMixingRecipeを生成することには {@link enchant.Class.MixingRecipe.createFromClass} を参照してください. 115 * @class 混ぜる先にどういう風に混ぜる処理を行うか設定する. 116 * このために、MixingRecipeはプロパティが三つある: 117 * <ul><li>decorateMethods(先の関数をラップする関数、デコレータ・パターンを参照してください)</li> 118 * <li>overrideMethods (先の関数をオーバーライドする関数)</li> 119 * <li>overrideProperties (先のプロパティをオーバーライドするプロパティ)</li></ul> 120 * <p>{@link enchant.Class.mixClasses}、 {@link enchant.Class.mixClassesFromRecipe} や {@link enchant.Class.applyMixingRecipe} を参照してください.</p> 121 * @param {Object} decorateMethods 先の関数をラップする関数、デコレータ・パターンを参照してください. 混ぜる結果のクラスでラップされた関数をアクセスするように_mixingというプロパティがある、例:this._mixing.myFunction.apply(this,arguments).<br>(キーと値のペア持っているオブジェクト、キーは関数名、値は関数). 122 * @param {Object} overrideMethods 先の関数をオーバーライドする関数.<br>(キーと値のペア持っているオブジェクト、キーは関数名、値は関数). 123 * @param {Object} properties 先のプロパティをオーバーライドするプロパティ.<br>(キーと値のペア持っているオブジェクト、キーは関数名、値はプロパティデスクリプタ). 124 * @property {Object} decorateMethods 先の関数をラップする関数、デコレータ・パターンを参照してください. 混ぜる結果のクラスでラップされた関数をアクセスするように_mixingというプロパティがある、例:this._mixing.myFunction.apply(this,arguments).<br>(キーと値のペア持っているオブジェクト、キーは関数名、値は関数). 125 * @property {Object} overrideMethods 先の関数をオーバーライドする関数.<br>(キーと値のペア持っているオブジェクト、キーは関数名、値は関数). 126 * @property {Object} overrideProperties 先のプロパティをオーバーライドするプロパティ.<br>(キーと値のペア持っているオブジェクト、キーは関数名、値はプロパティデスクリプタ). 127 * @example 128 * var recipe = new enchant.Class.MixingRecipe({ 129 * add : function(value) { 130 * this._myValue += 3*value; 131 * this._mixing.add.apply(this,arguments); 132 * }, 133 * mult : function(value) { 134 * this._myValue *= value*7; 135 * this._mixing.mult.apply(this,arguments); 136 * } 137 * },{ 138 * sub : function(value) { 139 * this._myValue -= 5*value; 140 * } 141 * },{ 142 * myProperty : { 143 * get: function() { 144 * return 3*this._myPropertyValue; 145 * }, 146 * set : function(val) { 147 * this._myPropertyValue = val; 148 * } 149 * }}); 150 * var NewClass = enchant.Class.applyMixingRecipe(Class1,recipe); 151 * @extends Object 152 * @constructs 153 */ 154 initialize : function(decorateMethods, overrideMethods, properties) { 155 this.decorateMethods = decorateMethods; 156 this.overrideMethods = overrideMethods; 157 this.overrideProperties = properties; 158 } 159 }); 160 161 /** 162 * 引数のクルスの関数やプロパティから、MixingRecipeを生成する. 163 * デフォルト振舞はsourceClassの全ての関数やプロパティ、スーパークラスの関数やプロパティも使用して、 164 * 混ぜる先の関数をラップする(decorate).<br>_mixingプロパティでラップされた関数が自動的に、 165 * sourceClassと混ぜる先の関数を呼び出されるので、関係なくてもいい. 166 * <p>対応引数でデフォルト振舞を変更ができる.</p> 167 * 168 * @param {Function<constructor enchant.Classで生成された関数>} sourceClass このクラスから、MixingRecipeが生成される. 169 * @param [boolean] onlyOwnProperties Trueの場合、スーパークラスの関数やプロパティが無視されない. 170 * @param [Array<String>] functionOverrideNameList 混ぜるときオーバーライドされる関数名を持っている配列. 171 * @param [Array<String>] functionIgnoreNameList MixingRecipeを生成するとき無視される関数名を持っている配列. 172 * @param [Array<String>] propertyIgnoreNameList MixingRecipeを生成するとき無視されるプロパティ名を持っている配列. 173 * @returns {enchant.Class.MixingRecipe} クラス定義から生成されたMixingRecipe. 174 * @example 175 * var recipe = enchant.Class.MixingRecipe.createFromClass(Class2, true, 176 * ['overrideFunction1','overrideFunction2'], 177 * ['ignoreFunction1','ignoreFunction2'], 178 * ['ignoreProperty1','ignorePropterty2']); 179 * recipe.overrideMethods['additionalFunction'] = new function() { 180 * console.log('Hello, World'); 181 * } 182 * recipe.overrideProperties['newProperty'] = { 183 * get: function() { 184 * return this._newProperty; 185 * }, 186 * set : function(val) { 187 * this._newProperty = val; 188 * } 189 * } 190 * var NewClass = enchant.Class.mixClassesFromRecipe(Class1,Class2,recipe); 191 * @constructs 192 * @static 193 */ 194 enchant.Class.MixingRecipe.createFromClass = function(sourceClass, onlyOwnProperties, functionOverrideNameList, functionIgnoreNameList, propertyIgnoreNameList) { 195 var decorate = {}; 196 var override = {}; 197 var properties = {}; 198 199 var source = sourceClass.prototype; 200 createFromPrototype(decorate,override,properties,source,onlyOwnProperties, functionOverrideNameList, functionIgnoreNameList, propertyIgnoreNameList); 201 return new enchant.Class.MixingRecipe(decorate,override,properties); 202 }; 203 204 /** 205 * 設定されたMixingRecipeを使用して、firstClassというクラスに実行して、この結果を戻る。設定されたMixingRecipeはsecondClassというクラスに関係があったほうがいい. 206 * どちらでもクラスの初期化関数を呼び出すデフォルト初期化関数が追加される. 207 * このデフォルト初期か関数の書式は:<br> 208 * ([firstClass コンストラクタ 引数 1],...,[firstClass コンストラクタ 引数 n],[secondClass コンストラクタ 引数 1],...[secondClass コンストラクタ 引数 n])</p> 209 * <p>どちらでもクラスが変更されない</p>{@link enchant.Class.MixingRecipe} を参照してください. 210 * @param {Function<constructor enchant.Classで生成された関数>} firstClass MixingRecipeを実行されるクラス. 211 * @param {Function<constructor enchant.Classで生成された関数>} sourceClass MixingRecipに関係があるクラス。デフォルト初期化関数に使用される。 212 * @param {enchant.Class.MixingRecipe} recipe firstClassに実行される製法。 secondClassに関係があったほうがいい. 213 * @param [Function] initializeMethod 設定すると、新しいクラスの初期化にデフォルト初期化関数が使用されないけど、この関数が使用される. 214 * @returns {Function<constructor enchant.Classで生成された関数>} 製法でどちらでもクラスを混ぜた結果クラス. 215 * @example 216 * var MapGroup = enchant.Class.mixClasses(Map, Group,true); 217 * var map = new MapGroup(16, 16); 218 * var SpriteLabel = enchant.Class.mixClasses(Sprite, Label,true); 219 * var kumaLabel = new SpriteLabel(32,32,'Kuma'); 220 * @static 221 */ 222 enchant.Class.mixClassesFromRecipe = function(firstClass, secondClass, recipe, initializeMethod) { 223 var result = enchant.Class.applyMixingRecipe(firstClass,recipe); 224 var paramLength = getFunctionParams(firstClass.prototype.initialize).length; 225 if(typeof(initializeMethod) !== 'function') { 226 initializeMethod = function() { 227 var args = Array.prototype.slice.call(arguments); 228 secondClass.prototype.initialize.apply(this,args.slice(paramLength)); 229 firstClass.prototype.initialize.apply(this,args.slice(0,paramLength)); 230 }; 231 } 232 result.prototype.initialize = initializeMethod; 233 return result; 234 }; 235 236 237 /** 238 * secondClassというクルスからMixingRecipeを生成して、firstClassというクラスに実行して、この結果を戻る. 239 * デフォルト振舞はsecondClassの全ての関数やプロパティ、スーパークラスの関数やプロパティも使用して、 240 * 混ぜる先の関数をラップする(decorate).<br>_mixingプロパティでラップされた関数が自動的に、 241 * sourceClassと混ぜる先の関数を呼び出されるので、関係なくてもいい. 242 * <p>その上、どちらでもクラスの初期化関数を呼び出すデフォルト初期化関数が追加される.このデフォルト初期か関数の書式は:<br> 243 * ([firstClass コンストラクタ 引数 1],...,[firstClass コンストラクタ 引数 n],[secondClass コンストラクタ 引数 1],...[secondClass コンストラクタ 引数 n])</p> 244 * <p>どちらでもクラスが変更されない</p>{@link enchant.Class.MixingRecipe} を参照してください. 245 * @param {Function<constructor enchant.Classで生成された関数>} firstClass MixingRecipeを実行されるクラス. 246 * @param {Function<constructor enchant.Classで生成された関数>} sourceClass このクラスから、MixingRecipeが生成される. 247 * @param [boolean] onlyOwnProperties Trueの場合、スーパークラスの関数やプロパティが無視されない. 248 * @param [Function] initializeMethod 設定すると、新しいクラスの初期化にデフォルト初期化関数が使用されないけど、この関数が使用される. 249 * @returns {Function<constructor enchant.Classで生成された関数>} どちらでもクラスを混ぜた結果クラス. 250 * @example 251 * var MapGroup = enchant.Class.mixClasses(Map, Group,true); 252 * var map = new MapGroup(16, 16); 253 * var SpriteLabel = enchant.Class.mixClasses(Sprite, Label,true); 254 * var kumaLabel = new SpriteLabel(32,32,'Kuma'); 255 * @static 256 */ 257 enchant.Class.mixClasses = function(firstClass, secondClass, useOnlyOwnPropertiesForSecondClass, initializeMethod) { 258 return enchant.Class.mixClassesFromRecipe(firstClass,secondClass,enchant.Class.MixingRecipe.createFromClass(secondClass, useOnlyOwnPropertiesForSecondClass, [], ['initialize'], []),initializeMethod); 259 }; 260 261 /** 262 * 設定されたMixingRecipeを先クラスに実行して、新たなクラスを生成してこのクラスを戻る. 263 * 先クラスが変更されない.<br>{@link enchant.Class.MixingRecipe} を参照してください. 264 * 265 * @param {Function<constructor enchant.Classで生成された関数>} target MixingRecipeを実行される先クラス. 266 * @param {enchant.Class.MixingRecipe} source 先に新しい機能を追加するMixingRecipe. 267 * @returns {Function<constructor enchant.Classで生成された関数>} sourceという製法とtargetというクラスを混ぜた結果クラス. 268 * @example 269 * var recipe = new enchant.Class.MixingRecipe({ 270 * // ... see enchant.Class.MixingRecipe 271 * },{ 272 * // ... see enchant.Class.MixingRecipe 273 * },{ 274 * // ... see enchant.Class.MixingRecipe 275 * }); 276 * var NewClass = applyMixingRecipe(Class1,recipe); 277 * @static 278 */ 279 enchant.Class.applyMixingRecipe = function(target, source) { 280 var result = enchant.Class.create(target,{}); 281 target = result.prototype; 282 for(var recipeKey in source) { 283 if(source.hasOwnProperty(recipeKey)) { 284 var currentSource = source[recipeKey]; 285 if(recipeKey === 'overrideMethods') { 286 for(var methodKey in currentSource) { 287 if(currentSource.hasOwnProperty(methodKey)) { 288 target[methodKey] = currentSource[methodKey]; 289 if(target._mixing && target._mixing[methodKey]) { 290 target._mixing[methodKey] = voidFunction; 291 } 292 } 293 } 294 } else if(recipeKey === 'overrideProperties') { 295 for(var propertyKey in currentSource) { 296 if(currentSource.hasOwnProperty(propertyKey)) { 297 Object.defineProperty(target,propertyKey,currentSource[propertyKey]); 298 } 299 } 300 } else if(recipeKey === 'decorateMethods') { 301 if(!target._mixing) { 302 target._mixing = {}; 303 } 304 for(var key in currentSource) { 305 if(currentSource.hasOwnProperty(key)) { 306 var targetHolder = target; 307 if(!target[key]) { 308 while(targetHolder instanceof Object && !targetHolder[key]) { 309 targetHolder = Object.getPrototypeOf(targetHolder); 310 } 311 } 312 if(target._mixing[key]) { 313 var newFunc = targetHolder[key]; 314 target._mixing[key] = (multipleMixingCombinationFunctionFactory(target._mixing[key],newFunc,key)); 315 } else { 316 target._mixing[key] = targetHolder[key]; 317 if(!target._mixing[key]) { 318 target._mixing[key] = voidFunction; 319 } 320 } 321 target[key] = currentSource[key]; 322 } 323 } 324 } 325 } 326 } 327 return result; 328 }; 329 })(); 330 } 331