1

I have a class where I want a simple factory method:

class GTree{ 

    public static createNode(){ 
        return new GNode(); 
    } 

} 

This means that I don't want to allow the consumer to immediately instantiate the GNode.

How do I properly implement this?

Obviously I can't do:

class GNode{
    constructor(){ 
        throw TypeError("This is nonsense"); 
    }
}

Because then I can't create nodes anymore at all.
How do I force using the factory?

html_programmer
  • 18,126
  • 18
  • 85
  • 158
  • What exactly do you need to protect against? Or are you just trying to set up a coding style? – Bergi Dec 08 '16 at 21:11
  • @Bergi I don't want consumers to instantiate nodes that are unrelated to tree instances. – html_programmer Dec 08 '16 at 21:12
  • What exactly does "unrelated" mean? Can you share the code for that? – Bergi Dec 08 '16 at 21:12
  • 1
    Obsession about restrictions never ends well. This approach will screw up testing and possible extensibility while offering nothing in return. You can keep GNode private and never export it. But again, this never ends well. Marking a class/method as *internal* in documentation is all that is usually needed. – Estus Flask Dec 08 '16 at 21:15
  • I want trees to keep internal reference to every node that is created through the lib (and vice versa). So I don't want any "dangling" nodes for which no state is kept in any tree. How would you propose I achieve this otherwise? – html_programmer Dec 08 '16 at 21:15
  • 2
    You can require that a `GTree` instance be passed to the constructor for a `GNode` so it can always register itself with that `GTree` and you can check that the object in question is a valid `GTree` object. Or, you could require a "secret" be passed to the `GNode` constructor which only your code knows (often a private object reference that is in scope of your module, but not public can work). Then, only your factory would know the secret so only the factory could call the constructor. – jfriend00 Dec 08 '16 at 21:27
  • @jfriend00 I like the idea of nodes registering themselves with the trees. But one other reason why I chose this approach was because I wanted to offer a nice api without requiring "new" for creating nodes. The second option was something I thought about too, but wasn't sure whether this would be too "hacky" to overcome the dilemma. – html_programmer Dec 08 '16 at 21:35
  • 2
    Oh, yet another method (besides those mentioned by @jfriend00) that I completely forgot about: [How to define private constructors in javascript](http://stackoverflow.com/q/21667149/1048572) – Bergi Dec 09 '16 at 01:34
  • Thanks @Bergi for providing this idea. After sleeping over it I reconsidered the dangling nodes because there may be use cases where they can be allowed. But this pattern will still be very helpful for another case that I have. – html_programmer Dec 09 '16 at 06:45

2 Answers2

2

You can't really do that in javascript, but you can do this:

export class GTree {
    public static createNode(name: string): GNode {
        return new GNodeImpl(name); 
    }
}

export interface GNode {
    name: string;
}

class GNodeImpl implements GNode {
    constructor(public name: string) {}
}

(code in playground)

Only GTree and the GNode interface are exported, meaning that it's not possible to instantiate GNodeImpl from outside the module.

I added the name property just for the example.

Nitzan Tomer
  • 155,636
  • 47
  • 315
  • 299
  • This answers my question, thanks. For my actual implementation, I will take into consideration the comments given and then decide. – html_programmer Dec 08 '16 at 21:31
2

Here's a simpler scheme than my earlier comments. Just define the GNode class in a private (but shared) scope and thus that's the only place the constructor can be called from and also reset the .constructor property so it doesn't leak out:

const GTree = (function() {
    class GNode {
        constructor() {

        }

        someOtherMethod() {
            console.log("someOtherMethod");
        }
    }
    // reset public .constructor
    GNode.prototype.constructor = function() {
        throw new Error("Can't call GNode constructor directly");
    };

    class GTree {
        constructor() {
            this.nodes = [];
        }

        createNode() {
            let node = new GNode();
            this.nodes.push(node);
            return node;
        }

        get length() {
            return this.nodes.length;
        }
    }
    return GTree;
})();


let tree = new GTree();
let node1 = tree.createNode();
let node2 = tree.createNode();
node1.someOtherMethod();
console.log(tree.length + " nodes");
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • 1
    Sorry, but [this is sooo wrong](http://stackoverflow.com/q/38739499/1048572). You're creating a new class every time the method is called; I don't think I have to explain why that's bad. And `let node3 = new (node2.constructor)()` is trivial… – Bergi Dec 09 '16 at 02:34
  • 1
    @Bergi - You were right. I've addressed both of those issues in the latest version. – jfriend00 Dec 09 '16 at 02:46
  • @KimGysen - I came up with a simpler/cleaner way to do this. – jfriend00 Dec 09 '16 at 05:30
  • Interesting, a private nested class would work. I reconsidered allowing dangling nodes to exist though. I have also taken in mind the comment of @Estus to allow future extensibility / testing, for which I need some reconsideration as well, if I want to make it an open library afterwards. – html_programmer Dec 09 '16 at 07:22
  • 1
    @KimGysen - That's fine that you've reconsidered your design for your own reasons, but I think I've provided a good answer to the question you originally asked which is how to limit creation to only the factory function. – jfriend00 Dec 09 '16 at 16:04
  • @jfriend00 Oh absolutely, it is a very useful piece of code and it is probable that I will use this or a variant if it turns out to be the best fit (design wise). Upvoted your answer, but not sure which one to accept since both answers seem viable solutions to the original question and it seems a bit unrespectful to uncheck the first answer. – html_programmer Dec 09 '16 at 17:00