Upload
chad-hietala
View
432
Download
1
Tags:
Embed Size (px)
DESCRIPTION
A look at inversion of control in JavaScript
Citation preview
Inversion Of Control
@chadhietalachadhietala.com
A Software Design Pattern
Invert the business logic flow through some sort of
assembler
Dependency Inversion
A Software Design Principle
Depend on abstractions, not concreations
Decouple your high level parts of your system from the low level parts by using interfaces.
Don’t call us. We’ll Call You.- Martin Fowler
So don’t do this...
function MyClass () {
this.multiply = new Multiplier();
}
Instead Inject
function MyClass () {
console.log(this.multiply); // Multiplier Instance
}
Reasons To Use
● Gain maintainability● APIs are more elegant & abstract● Easy to test● Gain Modularity
But how?
A Couple Different Approaches
Dependency Injection (DI)
IoC Containers&
DI In The Wild
At least in the JavaScript World
Introspective DI
At least in the JavaScript World
Look At The Signature
Angular
var App = angular.module('App');
App.controller('PostController', function ( $scope, $http ) {
$scope.posts = [];
$http.get('/posts').then( function ( posts ) {
$scope.posts = posts;
});
});
var App = angular.module('App');
App.controller('PostController', function ( $scope, $http ) {
$scope.posts = [];
$http.get('/posts').then( function ( posts ) {
$scope.posts = posts;
});
}); Injected
Angular
Angular: Under The Hood
Module Injector
● App begins to parse the HTML
● Discovers any directives● Looks at what objects
those directives point to● Pass the un-injected
functions to the injector
● Calls .toString on function● RegEx magic to check
what services to inject● Check registry● Creates instance with
injected items and caches
Non-injected Function
Angular: Under The Hood
Module Injector
● App begins to parse the HTML
● Discovers any directives● Looks at what objects
those directives point to● Pass the un-injected
functions to the injector
Non-injected Function
Inverted Control● Calls .toString on function● RegEx magic to check
what services to inject● Check registry● Creates instance with
injected items and caches
What About Mangling?
Angular Why U So Verbose?
angular.module('App', [ 'App.controllers' ]);
angular.module('App.controllers', [])
.controller( 'PostController', [ '$scope', '$http', function ( $scope, $http ) {
$scope.posts = [];
$http.get('/posts').then( function ( posts ) {
$scope.posts = posts;
});
}]);
Angular Why U So Verbose?
angular.module('App', [ 'App.controllers' ]);
angular.module('App.controllers', [])
.controller( 'PostController', [ '$scope', '$http', function ( $scope, $http ) {
$scope.posts = [];
$http.get('/posts').then( function ( posts ) {
$scope.posts = posts;
});
}]);
String representation of injections
$provider, $services, $values, & $factoriesangular.module('App', ['App.factories', 'App.controllers']);
angular.module('App.factories', [])
.factory('$session', function(){
var key = 'dskjsadljs3423243432';
return {
getKey: function () {
return key;
}
};
});
angular.controller('App.controller', [])
.controller('PostsController', [ '$scope', '$session', function ( $scope,
$session ) {
$scope.session = $session.getKey() // dskjsadljs3423243432
}]);
IoC Containers
Not the official logo
The container owns everything
Kills The Boilerplate
Ember
App = Ember.Application.create();
App.PostsRoute = Ember.Route.extend({
model: function () {
return this.store.get('posts');
}
});
Ember
App = Ember.Application.create();
App.PostsRoute = Ember.Route.extend({
model: function () {
return this.store.get('posts');
}
}); Injected
Ember: Under The Hood
Container
Initializers
Application
● Application creates container● Initializers register low-level
modules into the container
Ember: Under The Hood
Registry
InjectionsContainer
Lookup
● Modules are registered into the registry
● Injections are held in a separate map
● Lookup checks to see if the module is available and also if the requested module needs to be injected
● Lookup then creates the instance with the injections
Ember: Under The Hood
Registry
InjectionsContainer
● Modules are registered into the registry
● Injections are held in a separate map
● Lookup checks to see if the module is available and also if the requested module needs to be injected
● Lookup then creates the instance with the injectionsLookup
Inverted Control
Yo Dawg I Heard You Liked Containers...
A Closer Lookvar Session = Ember.Object.extend({
key: 'jkldsajlksldkjsjlad2312ekljk3'
});
var PostsController = Ember.Object.extend();
var PostController = Ember.Object.extend();
var container = new Ember.Container();
container.register('session:main', Session );
container.register('controller:posts', PostsController );
container.register('controller:post', PostController );
container.inject('controller', 'session', 'session:main');
var postController = container.lookup('controller:post');
postController.get('session.key') // jkldsajlksldkjsjlad2312ekljk3
A Closer Lookvar Session = Ember.Object.extend({
key: 'jkldsajlksldkjsjlad2312ekljk3'
});
var PostsController = Ember.Object.extend();
var PostController = Ember.Object.extend();
var container = new Ember.Container();
container.register('session:main', Session );
container.register('controller:posts', PostsController );
container.register('controller:post', PostController );
container.inject('controller', 'session', 'session:main');
var postController = container.lookup('controller:post');
postController.get('session.key') // jkldsajlksldkjsjlad2312ekljk3
All controllers
A Closer Lookvar Session = Ember.Object.extend({
key: 'jkldsajlksldkjsjlad2312ekljk3'
});
var PostsController = Ember.Object.extend();
var PostController = Ember.Object.extend();
var container = new Ember.Container();
container.register('session:main', Session );
container.register('controller:posts', PostsController );
container.register('controller:post', PostController );
container.inject('controller', 'session', 'session:main');
var postController = container.lookup('controller:post');
postController.get('session.key') // jkldsajlksldkjsjlad2312ekljk3
Add a property “session”
A Closer Lookvar Session = Ember.Object.extend({
key: 'jkldsajlksldkjsjlad2312ekljk3'
});
var PostsController = Ember.Object.extend();
var PostController = Ember.Object.extend();
var container = new Ember.Container();
container.register('session:main', Session );
container.register('controller:posts', PostsController );
container.register('controller:post', PostController );
container.inject('controller', 'session', 'session:main');
var postController = container.lookup('controller:post');
postController.get('session.key') // jkldsajlksldkjsjlad2312ekljk3
With the instance of ‘session:main’
A Closer Lookvar Session = Ember.Object.extend({
key: 'jkldsajlksldkjsjlad2312ekljk3'
});
var PostsController = Ember.Object.extend();
var PostController = Ember.Object.extend();
var container = new Ember.Container();
container.register('session:main', Session );
container.register('controller:posts', PostsController );
container.register('controller:post', PostController );
container.inject('controller', 'session', 'session:main');
var postController = container.lookup('controller:post');
postController.get('session.key') // jkldsajlksldkjsjlad2312ekljk3
A Closer Look cont.
var postsController = container.lookup('controller:posts');
postsController.get('session.key') //
jkldsajlksldkjsjlad2312ekljk3
console.log( postsController.container ) // Points to container
console.log( postController.container ) // Points to container
console.log( postController.container.lookup( 'controller:posts' ) ) // Points to the same posts controller instance in memory
Ember’s Elegant APIsApp.PostController = Ember.Controller.extend({
// Fetches the comments controller and then sets
// this.controllers.comments to the comments instance
needs: ['comments']
});
App.PostRoute = Ember.Route.extend({
setupController: function(){
// Returns the comments instance
var comments = this.controllerFor('comments');
// Returns the comments model
var commentsModel = this.modelFor('comments');
}
});
But Doesn’t AMD Solve This?
I Would Say No
Dependency Loading & Ordering
That isn’t IoC
AMD loaders are just simply dependency managers
They sit outside of the business domain.
AMD should not be your solutionfor systemic lookup of objects
Don’t use it as a hammer because…
you will commit SRP violations
define( [ 'backbone', 'views/a_view', 'views/a_sub_view', 'helpers/time_formatter', 'helpers/date_formatter', 'helpers/tokenizer' ], function ( Aview, SubView,
Time, Date, Tokenizer ) {
return Backbone.View.extend({
initialize: function ( ) {
this.time = new Time();
this.date = new Date();
this.tokenizer = new Tokenizer();
this.render();
},
render: function () {
var renderBuffer = [];
this.collection.each( function (list) {
var item = new SubView({ model: list });
renderBuffer.push( item.render().el );
});
this.el.append( renderBuffer )
}
});
});
Summary
● Deal with abstractions● Invert control and don’t deal with
concreations● AMD is just a loading tool
Fin.