Skip to content

Wrapping Patterns

Pablo Garcia edited this page Oct 13, 2017 · 16 revisions

WORK IN PROGRESS (DRAFT)

NOTE:

  • The documentation is still ongoing.
  • The examples can be wrong and change at any time.

Notes:

  1. a pattern needs another pattern without building.
  2. there is no point if it is the same pattern (is possible but doesn't do anything).
  3. Check memento's advanced use case.

Before you use this advanced functionality, you need to understand how the patterns that you want to wrap around each other work, because there could be cases where the functionality of the patterns can overlap, meaning methods can be named the same way, among other stuff, making it a better idea to create the patterns on their own and create a function that uses them accordingly.

Now, all the patterns in the core library are created using the createNew() method exported with the API (see advanced topics). createNew() returns a function that dynamically inherits from the options passed to the pattern. When this returned function is executed, it returns a builder object that contains everything needed to build the pattern's constructor.

For example, think about the following singleton:

var patterns = require('go-patterns');

var options = {
  constructor: function() {},
  publics: {},
  statics: {}
};

var mySingletonBuilder = patterns.singleton(options);

var Singleton = mySingletonBuilder.build();

We are simply obtaining the builder object and executing the method build() to create the pattern's constructor, which then can be used to create instances of. This builder object has the exact same format that we send as options to the pattern with the added functionality of a method called build():

var patternBuilderObject = {
  constructor: function PatternConstructor() {},
  statics: { /* ... */ },
  publics: { /* ... */ },
  build() { /* ... */ }
};

Do you see the resemblance now? Again, keep in mind that the builder object contains everything the pattern needs to be created, which was designed to have the same format of the options needed to be passed to any of the patterns. In other words, we can pass this builder object to another pattern and expect it to build itself on top of the current one.

For example, if I were to have a single factory of objects, you could do something as simple as:

var SingletonFactory = patterns.singleton(patterns.factory()).build();

Or more verbose:

function SomeFunctionConstructor() {}

var factoryBuilder = patterns.factory({
  constructor: SomeFunctionConstructor,
  publics: {},
  statics: {}
});

var SingletonFactory = patterns.singleton(factoryBuilder).build();

var mySingletonFactoryInstance = new SingletonFactory();

Easy, right? Now, you might think that the building of a pattern is then completely recursive, but it is not recursion as the build method is not called for every pattern but once. Remember, the build() method assumes that the builder object has everything it needs within the options.

Dynamic Inheritance and Wrapping Functionality

Now, how does the dynamic inheritance work? Well, it builds the pattern by inheriting from the options passed, meaning that the last pattern wrapping everything is the furthest most child.

And you can see that the dynamic inheritance works like a charm:

mySingletonFactoryInstance instanceof SingletonFactory; // => true
mySingletonFactoryInstance instanceof SomeFunctionConstructor; // => true

Notice that the Factory was never actually built in the example above, it was simply used as a basic mean to an end. So what if we needed to check if the instance was a child of Factory? There is still one thing you could do:

mySingletonFactoryInstance instanceof factoryBuilder.constructor; // => true

But do not be fooled by this, as it can lead you to believe that the pattern is already constructed, but it is still missing the static properties. Which raises the question; what if I still need to create the factory before the singleton, will I no longer be able to create the SingletonFactory? Well, as long as you have a hold on the builder object, you can pass it around and use it to create whatever you require.

For example, if we were to have the following code instead:

function SomeFunctionConstructor() {}

var factoryBuilder = patterns.factory({
  constructor: SomeFunctionConstructor,
  publics: {},
  statics: {}
});

var Factory = factoryBuilder.build();

But you realized you still need to create a singleton factory after that, you could simply:

var SingletonFactory = patterns.singleton(factoryBuilder).build();

var mySingletonFactoryInstance = new SingletonFactory();

And now:

mySingletonFactoryInstance instanceof SingletonFactory; // => true
mySingletonFactoryInstance instanceof SomeFunctionConstructor; // => true
mySingletonFactoryInstance instanceof Factory; // => true

Be aware: using the same builder object to build multiple patterns has an unfortunate side effect! The build() method has just one task to do, which is extremely important, and that is: extending the statics object into the pattern's constructor of the pattern being built. In this case, we just built two patterns that have the same static options, which might not be what was intended in the first place.

Remember, when wrapping patterns, only the outermost pattern's prototype will contain everything within the publics object, and only the outermost pattern's constructor will contain everything within the statics object. This was designed this way because of the nature of "prototype" object in JavaScript.

A way to get around this could be to manipulate the options manually like this:

var SingletonFactory = patterns.singleton(Object.assign(
  factoryBuilder,
  { statics: { /* ... */ } }
)).build();

One last thing, all this time I've mentioned that the builder object contains everything needed to build the pattern, but in reality, it contains anything needed to wrap patterns on top of each other, meaning that it does not use the options within the builder object but actually holds a closure to each of the options needed. In other words, if you were to replace the options within the builder object, it wouldn't matter, but if you modify any of the options, as they point to the same object, it will be reflected in the pattern's definition.