From 63f2562e51c374e480ec1e10b8f8cf82f8b08e3a Mon Sep 17 00:00:00 2001 From: Armand Abric Date: Wed, 19 Oct 2016 12:33:50 +0200 Subject: [PATCH] Add the abilities to destroy the container --- README.md | 1 + src/Container.js | 40 ++++++++++++++++++++++++--- tests/fixture/valid/ServiceF.js | 14 ++++++++++ tests/functionals/ContainerSpec.js | 43 ++++++++++++++++++++++++++++++ 4 files changed, 95 insertions(+), 3 deletions(-) create mode 100644 tests/fixture/valid/ServiceF.js diff --git a/README.md b/README.md index ab6a4b8..e505642 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,7 @@ var fooServiceInstance = container.getService('foo'); var barParameterValue = container.getParameter('bar'); +constainer.destroy(); ``` ## Configuration diff --git a/src/Container.js b/src/Container.js index f13e713..3ecd40e 100644 --- a/src/Container.js +++ b/src/Container.js @@ -16,6 +16,7 @@ var Container = function Container(serviceDefinitionCollection, parameterCollect this.parameterCollection = parameterCollection; this.serviceStorage = new ServiceStorage(); + this.isDestroyed = false; if (validateContainer) { this.serviceDefinitionCollection.checkCyclicDependencies(); @@ -31,7 +32,9 @@ var Container = function Container(serviceDefinitionCollection, parameterCollect * @return {*|null} */ Container.prototype.getParameter = function (name) { - if (!this.parameterCollection.hasParameter(name)) { + if (this.isDestroyed) { + throw new Error('This container instance has been destroyed.'); + } else if (!this.parameterCollection.hasParameter(name)) { throw new Error('Unknown parameter "' + name + '".'); } @@ -47,7 +50,9 @@ Container.prototype.getParameter = function (name) { * @return {*} */ Container.prototype.getService = function (name) { - if (!this.serviceDefinitionCollection.hasServiceDefinition(name)) { + if (this.isDestroyed) { + throw new Error('This container instance has been destroyed.'); + } else if (!this.serviceDefinitionCollection.hasServiceDefinition(name)) { throw new Error('Unknown service "' + name + '".'); } @@ -77,7 +82,9 @@ Container.prototype.getService = function (name) { * @param {Function} mock */ Container.prototype.mockService = function (name, mock) { - if (!this.serviceDefinitionCollection.hasServiceDefinition(name)) { + if (this.isDestroyed) { + throw new Error('This container instance has been destroyed.'); + } else if (!this.serviceDefinitionCollection.hasServiceDefinition(name)) { throw new Error('Unknown service "' + name + '".'); } @@ -89,4 +96,31 @@ Container.prototype.mockService = function (name, mock) { this.serviceStorage.replaceInstance(name, mock); }; +/** + * Will try to destroy all services by calling the method `destructor` on it (if + * it exist) to ask them nicely to destroy themself and by removing the internal + * reference to the service to simplify the life of the garbage colletor. + * The container will also be inusable. + */ +Container.prototype.destroy = function () { + if (this.isDestroyed) { + throw new Error('This container instance has already been destroyed.'); + } + + this.isDestroyed = true; + + var self = this; + this.serviceDefinitionCollection.forEach(function (serviceDefinition) { + if (self.serviceStorage.hasInstance(serviceDefinition.getName())) { + var serviceInstance = self.serviceStorage.getInstance(serviceDefinition.getName()); + + if (typeof serviceInstance.destructor === 'function') { + serviceInstance.destructor(); + } + } + + delete self.serviceStorage; + }); +}; + module.exports = Container; diff --git a/tests/fixture/valid/ServiceF.js b/tests/fixture/valid/ServiceF.js new file mode 100644 index 0000000..4c8e586 --- /dev/null +++ b/tests/fixture/valid/ServiceF.js @@ -0,0 +1,14 @@ +'use strict'; + +/** + * A destroyable service. + * + * @constructor + */ +var ServiceF = function ServiceF() { +}; + +ServiceF.prototype.destructor = function () { +}; + +module.exports = ServiceF; diff --git a/tests/functionals/ContainerSpec.js b/tests/functionals/ContainerSpec.js index d3692c0..ee1856e 100644 --- a/tests/functionals/ContainerSpec.js +++ b/tests/functionals/ContainerSpec.js @@ -13,6 +13,7 @@ var ServiceDefinitionCollection = require('./../../src/ServiceDefinitionCollecti var servicesConfigurationValid = require('./../fixture/valid/services'); var ServiceA = require('./../fixture/valid/ServiceA'); var ServiceC = require('./../fixture/valid/ServiceC'); +var ServiceF = require('./../fixture/valid/ServiceF'); describe('Container', function () { it('should return the parameter value', function () { @@ -162,4 +163,46 @@ describe('Container', function () { it.skip('should replace a cached service by a mocker one', function () { // TODO }); + + it('should be inusable after it\'s destruction', function () { + var serviceDefinitionCollection = new ServiceDefinitionCollection(); + var parameterCollection = new ParameterCollection(); + + var container = new Container(serviceDefinitionCollection, parameterCollection); + container.destroy(); + + expect(() => { + container.getParameter() + }).to.throw('This container instance has been destroyed.'); + + expect(() => { + container.getService() + }).to.throw('This container instance has been destroyed.'); + + expect(() => { + container.mockService() + }).to.throw('This container instance has been destroyed.'); + }); + + it('should invoke the `destructor` method on a service when the container is destroyed', function () { + var serviceDefinitionF = new ServiceDefinition( + 'foo.serviceF', + ServiceF, + new FunctionArgumentCollection(), + true, + new CallCollection() + ); + + var serviceDefinitionCollection = new ServiceDefinitionCollection([serviceDefinitionF]); + var parameterCollection = new ParameterCollection(); + + var container = new Container(serviceDefinitionCollection, parameterCollection); + + var serviceFInstance = container.getService('foo.serviceF'); + var destructorSpy = sinon.spy(serviceFInstance, 'destructor'); + + container.destroy(); + + expect(destructorSpy).to.have.been.calledOnce; + }); });