3

I have multiple eatable classes in javascript eg: food, drinks, snacks. Each of this class requires a different set of parameters. I have another factory class which creates an instance of the eatable item that is sent to it.

I am not able to figure out how can we dynamically select the eatable item and pass the arguments (which are in an array form) using this factory?

I have come up with two solutions - Solution 1:

var factory = function(eatable, argumentList){
  var obj = new eatable(argumentList);
  return obj
};

This is a problem because argumentList is an array.

Solution 2

var factory = function(eatable, argumentList){
  var obj =  eatable.apply({}, argumentList);
  return obj
};

this does not really create an object of the eatable type.

The effect that I really want Say I am able to convert the argumentList into a js argument type object then -

var obj = new eatable(argumentList.toArguments());
obj instanceOf eatable; // should return true

Please help!

tusharmath
  • 10,622
  • 12
  • 56
  • 83
  • 2
    What's the point of the factory function if you have to pass it both a reference to the required "class" and the argument list? – nnnnnn Jun 27 '13 at 12:00
  • I have a really long list of classes that I want to initialize. I don't want to manually write code for initializing them instead I just pass and array which contains the reference of the class and the parameters that must be supplied to constructor. – tusharmath Jun 27 '13 at 12:06
  • Yes, but given that both of your current possible solutions are basically one-line functions (not counting the return statement) you may as well just have that one line in whatever loop processes the array. (Although yes, I know you're not happy with those solutions.) – nnnnnn Jun 27 '13 at 12:13
  • Actually that is what I am doing. The factory method is called for each item in the list. – tusharmath Jun 27 '13 at 12:16
  • I understand that. I'm saying the factory function seems redundant when its (very short) body could go directly in your loop. I suppose you don't want to change your contructors so that they can accept an array and you can just use Solution 1? – nnnnnn Jun 27 '13 at 12:32
  • Yes, I don't want to change the constructors. – tusharmath Jun 27 '13 at 12:43
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/32491/discussion-between-tushar-mathur-and-nnnnnn) – tusharmath Jun 27 '13 at 12:57
  • duplicate of [Use of .apply() with 'new' operator. Is this possible?](http://stackoverflow.com/questions/1606797/use-of-apply-with-new-operator-is-this-possible) – Bergi Jun 27 '13 at 13:55

5 Answers5

4

Ah, yes. I've encountered this problem before - you can't use new and apply together in JavaScript. A similar question has been asked before: Use of .apply() with 'new' operator. Is this possible?

The problem is quite apparent - new is a keyword, not a function; and apply can only be used on a function. If new was a function instead of a keyword then we could use it in conjuction with apply.

To understand how to do so let's create a function called new which does exactly what the keyword new does:

Function.prototype.new = (function () {
    function Factory(constructor, args) {
        return constructor.apply(this, args);
    }

    return function() {
        Factory.prototype = this.prototype;
        return new Factory(this, arguments);
    };
}());

Now instead of calling a constructor as follows:

var object = new constructor(arg1, ...);

You can call a constructor as follows:

var object = constructor.new(arg1, ...);

What's the advantage of doing so you ask? Well it's simple really. Because new is now a function instead of a keyword you can use it in conjunction with apply as follows:

var object = Function.new.apply(constructor, [arg1, ...]);

Hence your eatable factory function now becomes:

var factory = function(eatable, argumentList) {
    var obj = Function.new.apply(eatable, argumentList);
    return obj;
};

Edit: If all your factory function does is take an eatable constructor and an argumentList and return new.apply(eatable, argumentList) then as Bergi pointed out in his comment you could define factory as follows instead:

var factory = Function.apply.bind(Function.new);

Hope this helped.

Community
  • 1
  • 1
Aadit M Shah
  • 72,912
  • 30
  • 168
  • 299
  • 1
    `var factory = Function.prototype.apply.bind(Function.prototype.new);` :-) – Bergi Jun 27 '13 at 14:24
  • btw don't forget voting to close as a duplicate if you've already found that question… – Bergi Jun 27 '13 at 14:26
  • 1
    @Bergi You could also do `var factory = Function.apply.bind(Function.new);` because `Function` itself is an instance of `Function.prototype`. What a conundrum. – Aadit M Shah Jun 27 '13 at 14:26
  • Oops of course. I had thought you were relying on [non-standard generics](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array#Array_generic_methods) at first :-/ – Bergi Jun 27 '13 at 14:29
3

You can use Object.create to set up the prototype chain correctly:

function factory(eatable, argumentList){
    var obj = Object.create(eatable.prototyope);
    return eatable.apply(obj, argumentList) || obj;
}

This is basically what the new operator does.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
1

You can define a function init to initialize the object .

function Eatable(){

}

Eatable.prototype.init = function(/** arg1, arg2, arg3 **/){
    //  initialize object
}

In factory function

var eatable = new Eatable();
eatable.init.apply(eatable, /** pass arguments array here **/);
return eatable;
Diode
  • 24,570
  • 8
  • 40
  • 51
  • What is Eatable? In my question Eatable is just a variable and not a class. – tusharmath Jun 27 '13 at 12:13
  • 1
    But you could use this idea with your `Food()`, `Snacks()`, etc. classes: define each one as an empty constructor with an associated `init()` function... – nnnnnn Jun 27 '13 at 12:14
  • @Tushar Mathur : I just used `Eatable` as a class definition just to demonstrate how a separate `init` method can be invoked with `apply` and pass parameters as arguments. – Diode Jun 27 '13 at 12:18
  • @nnnnnn Why would I want to modify the existing classes with an empty constructor? This is not a solution, its an alternative which will cause changes in all the classes. – tusharmath Jun 27 '13 at 12:20
  • 1
    Why would you want to? Because this is one way to implement your concept of a factory function that can pass through arguments. I was just trying to answer your first comment by explaining how this concept related to your classes, not saying this is necessarily the best possible option for you. – nnnnnn Jun 27 '13 at 12:29
0

You have to provide context to apply, The context is the object you are trying to apply the arguments to. The context you are currently passing {} is of type Object

var factory = function(eatable, argumentList){
  var obj =  eatable.apply(new Eatable(), argumentList);
  return obj
};

I can not use factories with out polymorphism so if you didn't create those eatables in way they extend an Eatalbe object you will not be able to do it.

raam86
  • 6,785
  • 2
  • 31
  • 46
  • I want to create a new object of Food, Drinks etc. depending upon what has been passed as an argument in the eatable variable. Eatable is not a class in it self. – tusharmath Jun 27 '13 at 12:04
  • "Should be"? No, that's just one way to do it - even the article you linked to mentions other ways that don't involve sub-classing. In JS it is reasonably common to go with duck typing... – nnnnnn Jun 27 '13 at 12:07
  • Why would you want to force polymorphism? There is absolutely no relationship between the classes. – tusharmath Jun 27 '13 at 12:08
  • Wouldn't this work with `eatable.apply(new eatable(), argumentList);` - assuming all of the contructors that `eatable` might refer to will "work" with no arguments. – nnnnnn Jun 27 '13 at 12:30
  • @nnnnnn Are you asking me this? – raam86 Jun 27 '13 at 12:37
  • @nnnnnn say the returned value is saved in X. 'X instance of Drinks' should be true incase eatable is Drinks class. – tusharmath Jun 27 '13 at 12:47
0

One more way to achieve this is as follows -

var _bind = Function.prototype.bind;
var factory = function(_constructor, _argumentList){
  var obj = _bind.apply(_constructor, [null].concat(_argumentList));
  return obj
};
tusharmath
  • 10,622
  • 12
  • 56
  • 83