Inversion Of Control

52
Inversion Of Control @chadhietala chadhietala.com

description

A look at inversion of control in JavaScript

Transcript of Inversion Of Control

Page 1: Inversion Of Control

Inversion Of Control

@chadhietalachadhietala.com

Page 2: Inversion Of Control

A Software Design Pattern

Page 3: Inversion Of Control

Invert the business logic flow through some sort of

assembler

Page 4: Inversion Of Control

Dependency Inversion

Page 5: Inversion Of Control

A Software Design Principle

Page 6: Inversion Of Control

Depend on abstractions, not concreations

Page 7: Inversion Of Control

Decouple your high level parts of your system from the low level parts by using interfaces.

Page 8: Inversion Of Control

Don’t call us. We’ll Call You.- Martin Fowler

Page 9: Inversion Of Control

So don’t do this...

function MyClass () {

this.multiply = new Multiplier();

}

Page 10: Inversion Of Control

Instead Inject

function MyClass () {

console.log(this.multiply); // Multiplier Instance

}

Page 11: Inversion Of Control

Reasons To Use

● Gain maintainability● APIs are more elegant & abstract● Easy to test● Gain Modularity

Page 12: Inversion Of Control

But how?

Page 13: Inversion Of Control

A Couple Different Approaches

Page 14: Inversion Of Control

Dependency Injection (DI)

IoC Containers&

Page 15: Inversion Of Control

DI In The Wild

At least in the JavaScript World

Page 16: Inversion Of Control

Introspective DI

At least in the JavaScript World

Page 17: Inversion Of Control

Look At The Signature

Page 18: Inversion Of Control

Angular

var App = angular.module('App');

App.controller('PostController', function ( $scope, $http ) {

$scope.posts = [];

$http.get('/posts').then( function ( posts ) {

$scope.posts = posts;

});

});

Page 19: Inversion Of Control

var App = angular.module('App');

App.controller('PostController', function ( $scope, $http ) {

$scope.posts = [];

$http.get('/posts').then( function ( posts ) {

$scope.posts = posts;

});

}); Injected

Angular

Page 20: Inversion Of Control

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

Page 21: Inversion Of Control

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

Page 22: Inversion Of Control

What About Mangling?

Page 23: Inversion Of Control

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;

});

}]);

Page 24: Inversion Of Control

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

Page 25: Inversion Of Control

$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

}]);

Page 26: Inversion Of Control

IoC Containers

Not the official logo

Page 27: Inversion Of Control

The container owns everything

Page 28: Inversion Of Control

Kills The Boilerplate

Page 29: Inversion Of Control

Ember

App = Ember.Application.create();

App.PostsRoute = Ember.Route.extend({

model: function () {

return this.store.get('posts');

}

});

Page 30: Inversion Of Control

Ember

App = Ember.Application.create();

App.PostsRoute = Ember.Route.extend({

model: function () {

return this.store.get('posts');

}

}); Injected

Page 31: Inversion Of Control

Ember: Under The Hood

Container

Initializers

Application

● Application creates container● Initializers register low-level

modules into the container

Page 32: Inversion Of Control

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

Page 33: Inversion Of Control

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

Page 34: Inversion Of Control

Yo Dawg I Heard You Liked Containers...

Page 35: Inversion Of Control

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

Page 36: Inversion Of Control

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

Page 37: Inversion Of Control

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”

Page 38: Inversion Of Control

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’

Page 39: Inversion Of Control

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

Page 40: Inversion Of Control

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

Page 41: Inversion Of Control

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');

}

});

Page 42: Inversion Of Control

But Doesn’t AMD Solve This?

Page 43: Inversion Of Control

I Would Say No

Page 44: Inversion Of Control

Dependency Loading & Ordering

Page 45: Inversion Of Control

That isn’t IoC

Page 46: Inversion Of Control

AMD loaders are just simply dependency managers

Page 47: Inversion Of Control

They sit outside of the business domain.

Page 48: Inversion Of Control

AMD should not be your solutionfor systemic lookup of objects

Page 49: Inversion Of Control

Don’t use it as a hammer because…

Page 50: Inversion Of Control

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 )

}

});

});

Page 51: Inversion Of Control

Summary

● Deal with abstractions● Invert control and don’t deal with

concreations● AMD is just a loading tool

Page 52: Inversion Of Control

Fin.