Inversion Of Control

Post on 10-May-2015

432 views 1 download

Tags:

description

A look at inversion of control in JavaScript

Transcript of Inversion Of Control

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.