JavaScript | Angular | Mediator Pattern: Tactics for Obviating Circular Dependencies In AngularJS

大憨熊 提交于 2019-12-08 05:46:02

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!