Andrew Bessa
Andrew Bessa

Reputation: 271

JavaScript module pattern with sub-modules cross access or better pattern?

Perhaps this is the wrong approach to my problem, but that is why I'm here. In the code below is a sample of a JavaScript module pattern with sub-modules. As I build this, I realize that some sub-modules need to "call" each other's methods.

I know that it would be wrong to use the full call admin.subModuleB.publicB_2(); but its the only way since the IIFE functions cannot call "themselves" until instatiated, ex. "module" is not available in the primary namespace, etc...

My thought is that this pattern is incorrect for my situation. The purpose of the module encapsulation is to keep things private unless reveled. So what would be a better pattern?

var module = (function($, window, document, undefined) {

    return {
        subModuleA : (function() {
            var privateA = 100;
            return {
                // We have access to privateA
                publicA_1 : function() {
                    console.log(privateA);
                    // How do I use a method from publicB_1
                    // the only way is:
                    module.subModuleB.publicB_2();
                    // but I don't want to use "module"
                },
                publicA_2 : function() {
                    console.log(privateA);
                }
            }
        })(),
        subModuleB : (function() {
            var privateB = 250;
            return {
                // We have access to privateB
                publicB_1 : function() {
                    console.log(privateB);
                },
                publicB_2 : function() {
                    console.log(privateB);
                    // I have access to publicB_1
                    this.publicB_1();
                }
            }
        })()
    }

})(jQuery, window, document);

Upvotes: 2

Views: 355

Answers (1)

Greg Burghardt
Greg Burghardt

Reputation: 18783

What you actually have is an issue with dependencies. Sub module A has a dependency on Sub module B. There are two solutions that come to mind.

  1. Define both modules as their own variables inside the function closure, but return them together in a single object.

  2. What you actually want is instantiable classes where Class A has a dependency on Class B.

Since solution #1 is the closest to your current code, let's explore that first.

Define Both Modules Separately Inside the Closure

var module = (function($, window, document, undefined) {

    var SubModuleA = function() {
        var privateA = 100;

        return {
            // We have access to privateA
            publicA_1 : function() {
                console.log(privateA);
                // Refer to SubModuleB via the private reference inside your "namespace"
                SubModuleB.publicB_2();
                // but I don't want to use "module"
            },
            publicA_2 : function() {
                console.log(privateA);
            }
        };
    }();

    var SubModuleB = function() {
        var privateB = 250;

        return {
            // We have access to privateB
            publicB_1 : function() {
                console.log(privateB);
            },
            publicB_2 : function() {
                console.log(privateB);
                // I have access to publicB_1
                this.publicB_1();
            }
        };
    }();

    // Return your module with two sub modules
    return {
        subModuleA : SubModuleA,
        subModuleB : SubModuleB
    };

})(jQuery, window, document);

This allows you to refer to your two sub modules using local variables to your module's closure (SubModuleA and SubModuleB). The global context can still refer to them as module.subModuleA and module.subModuleB.

If Sub Module A uses Sub Module B, it begs the question of whether or not Sub Module B needs to be revealed to the global context at all.

To be honest, this is breaking encapsulation because not all the functionality of Sub Module A exists in Sub Module A. In fact, Sub Module A cannot function correctly without Sub Module B.

Given your particular case, the Module Pattern seems to be an Anti Pattern, that is, you are using the wrong tool for the job. In reality, you have two classifications of objects that are interdependent. I would argue that you need "classes" (JavaScript Constructor functions) and traditional OOP practices.

Use JavaScript Constructor Functions ("classes")

First, let's refactor your "module" into two classes:

var module = (function($, window, document, undefined) {

    function ClassA(objectB) {
        var privateA = 100;

        this.publicA_1 = function() {
            console.log(privateA);
            objectB.publicB_2();
        };

        this.publicA_2 = function() {
            console.log(privateA);
        };
    }

    function ClassB() {
        var privateB = 250;

        this.publicB_1 = function() {
            console.log(privateB);
        };

        this.publicB_2 = function() {
            console.log(privateB);
            this.publicB_1();
        };
    }

    // Return your module with two "classes"
    return {
        ClassA: ClassA,
        ClassB: ClassB
    };

})(jQuery, window, document);

Now in order to use these classes, you need some code to generate the objects from the constructor functions:

var objectA = new module.ClassA(new module.ClassB());
objectA.publicA_1();
objectA.publicA_2();

This maximizes code reuse, and because you are passing an instance of module.ClassB into the constructor of module.ClassA, you are decoupling those classes from one another. If you don't want outside code to be managing dependencies, you can always tweak ClassA thusly:

function ClassA() {
    var privateA = 100,
        objectB = new ClassB();

    this.publicA_1 = function() {
        console.log(privateA);
        objectB.publicB_2();
    };

    this.publicA_2 = function() {
        console.log(privateA);
    };
}

Now you can refer to module.ClassB using the name within the function closure: ClassB. The advantage here is that outside code does not have to give module.ClassA all of its dependencies, but the disadvantage is that you still have ClassA and ClassB coupled to one another.

Again, this begs the question of whether or not the global context needs ClassB revealed to it.

Upvotes: 4

Related Questions