问题
Ways to Implement Dependency Inversion Principle in AngularJS
Case Study: The Mediator Pattern
Normally, implementing a Mediator/Director entails both Director and Colleague(s) having a reference to each other. Not difficult when Director is responsible for Module-Lifecycle Management (MLM):
Director creates Colleagues while passing itself in during construction:
var Director = function Director($$) {
...
function init(...) {
_moduleA = new ModuleA(this);
_moduleB = new ModuleX(this);
_moduleX = new ModuleX(this);
}
function moduleChanged(ref) {
if (ref === _moduleXYZ) _moduleC.run(ref.datum);
}
return this;
};
The Tricky Part: Angular
AngularJS automatically takes on the burden of MLM, so its tough to use the approach above. We could modify our Director.init method slightly:
_moduleX = $dependency;
...
_moduleX.init(this);
But [my] Colleagues need a reference to Director at construction-time.
Question and Explanation
While my Director is implemented as EDM (Event-Driven Mediation) to run away from needing DIP, I now need Colleagues to reference Director:
var Colleague = function Colleague(director) {
var thus = this;
...
director.publish(director.CHANNELS.SOME_CHANNEL_NAME, data);
...
return this;
};
This is because Director must now provide Module Authorization (MA):
var Director = function Director($$) {
...
function subscribe(channel, handler) {
var args = Array.prototype.slice.call(arguments, 0);
$rootScope.$on.apply($rootScope, args);
return this;
}
function publish(channel) {
var args = Array.prototype.slice.call(arguments, 0);
if (channel in this.CHANNELS) $rootScope.$broadcast.apply($rootScope, args);
return this;
}
return this;
};
That said, there's virtually NO-DIFFERENCE -- as far as DIP/DI is concerned -- between this approach and the more Classical, object-reference driven approach first mentioned.
So my question is, what are different ways that I can elegantly impose DIP and MLM in Angular without running into problems with the framework?
Concerns & Potential Workarounds
_colleague.init(this);
: BAD_colleague = Colleague.call($dependency, this)
: Healthy for Singletons???- Split state-driven mediation and apply Director Interface to each Colleague
- Mediator Abstract Class [SEE below]
Apply Director Interface to each Colleague
Import (DI) a Mediator class into each Colleague:
var Mediator = function Mediator($rootScope) {
...
function subscribe(channel, handler) {
var args = Array.prototype.slice.call(arguments, 0);
$rootScope.$on.apply($rootScope, args);
return this;
}
function publish(channel) {
var args = Array.prototype.slice.call(arguments, 0);
if (channel in this.CHANNELS.SOME_CHANNEL_NAME) $rootScope.$broadcast.apply($rootScope, args);
return this;
}
return this;
};
angular.factory('Mediator', () => Mediator);
var Colleague = function Colleague(Mediator) {
var thus = this;
...
this.publish(this.CHANNELS.SOME_CHANNEL_NAME, data);
// export precepts
Mediator.apply(this);
this.init = init;
...
return this;
};
But this could deprive Director of opportunities to mediate based upon high-level application-state, since it won't be a Singleton. E.G:
if (this.state.patientInfoStepComplete) _module.run(ref.datum);
Abstract Mediator Class
As in GoF, the Participants include a Mediator and a ConcreteMediator.
Mediator:
- Defines an Interface for communication with Colleague objects
ConcreteMediator:
- Implements cooperative behavior by coordinating Colleague objects
- knows and maintains its colleagues
To follow a little closer to DIP, can we take "this.CHANNELS
" and encapsulate it in Mediator, but ConcreteMediator would still need a reference to it and Colleague would need a reference to whatever is providing both .CHANNELS
and .publish(...)
.
You may wonder why I need a "Concrete Mediator" when I'm already implementing something of an "Abstract Mediator" through the use of .CHANNELS
which can provide Request-Routing anyway since its keys are used by Colleagues to abstract a Channel-Name value. The reason is that, while .CHANNELS
can route one request to another, it can only do that -- one single request -- while .publish(...)
can provide one-to-many handling AND work as middleware in other ways as well (e.g: MA).
var _actions = {
'SOME_CHANNEL_NAME': (e, a, b, c) => {
$rootScope.emit('a', a);
$rootScope.emit('b', b);
$rootScope.emitemit('c', c);
},
};
function publish() {
...
if (channel in this.CHANNELS) _actions[channel].apply(_actions, args);
...
}
Any ideas or patterns you've used or can think of?
Any cool, non-standard tricks for registering/accessing dependencies in Angular?
Any way in Angular to configure another dependency to be injected to Services at run-time?
Call angular.constant('contreteMediator', this);
at run-time inside of Director?
Middleware for the Provider-Construction process?
#PreThanks
来源:https://stackoverflow.com/questions/37889533/javascript-angular-mediator-pattern-tactics-for-obviating-circular-dependen