Проблемы с круговой зависимостью и ООП в AngularJS
AngularJS + OOP - своего рода сексуальная функция для использования
Привет, я успешно использую OOP с AngularJs уже некоторое время (сначала начал с angularjs с наследованием oop в действии), предоставленный подход позволяет вам определить ваш классы как службы angular, которые вы можете впоследствии расширить или наследовать от этого:
Application.factory('AbstractObject', [function () {
var AbstractObject = Class.extend({
virtualMethod: function() {
alert("Hello world");
},
abstractMethod: function() { // You may omit abstract definitions, but they make your interface more readable
throw new Error("Pure abstract call");
}
});
return AbstractObject; // You return class definition instead of it instance
}]);
Application.factory('DerivedObject', ['AbstractObject', function (AbstractObject) {
var DerivedObject = AbstractObject.extend({
virtualMethod: function() { // Shows two alerts: `Hey!` and `Hello world`
alert("Hey!");
this._super();
},
abstractMethod: function() {
alert("Now I'm not abstract");
}
});
return DerivedObject;
}]);
Plunker: http://plnkr.co/edit/rAtVGAsNYggBhNADMeoT
с использованием описанного подхода дает вам возможность определять классы, которые прекрасно интегрируются в инфраструктуру angular. Вы получаете всевозможные отличные функции из двух миров - ООП и AngularJs. Инъекционная инъекция бесплатна для ваших классов, и это делает ваши классы простыми, позволяет помещать много кода контроллера шаблона в некоторый базовый класс, который может быть впоследствии использован повторно.
Однако
Инфраструктура AngularJs блокирует ранее описанный подход от распространения его крыльев на всех 100%. Проблема возникает, когда вы пытаетесь определить рекурсивные определения классов (например, рекурсивную агрегацию), например, у вас есть два определения класса, такие как Blog
и Tag
Application.factory('Blog', ['Tag', function (Tag) {
var Blog = Class.extend({
tags: function() {
return this.tags;
}
});
return Blog;
}]);
Application.factory('Tag', ['Blog', function (Blog) {
var Tag = Class.extend({
Blogs: function() {
return this.blogs;
}
});
return Tag;
}]);
Это не сработает, потому что оба Blog
и Tag
сами ссылаются на себя, вызывая круговую зависимость.
P.S
Последнее, что я нашел своеобразное уродливое решение, которое решает мою проблему в моем конкретном случае, но не работает вообще, и, как я уже сказал, это некрасиво:
Application.factory('BlogNamespace', [function () {
var Blog = Class.extend({
tags: function() {
return this.tags;
}
});
var Tag = Class.extend({
Blogs: function() {
return this.blogs;
}
});
return {
Tag: Tag,
Blog: Blog
};
}]);
Вопрос
Вышеупомянутое исправление не будет работать, поскольку пространства имен также могут быть предметом круговой зависимости. Это означает, что это не решение описанной проблемы, а проблема на уровне ниже уровня.
Любые предложения о том, как можно решить описанную проблему в общем случае?
Ответы
Ответ 1
Круговая зависимость всегда является признаком <сильного > смешения проблем, что очень плохо. Мишко Хевери, один из авторов AngularJS, объясняет приятное решение в своем удивительном блоге. Короче говоря, у вас, вероятно, есть какая-то третья служба, которая является единственной частью вашего кода, которая действительно нужна двум другим.
Ответ 2
Я отвечаю на свой вопрос только потому, что нашел технический способ решения проблемы, о которой я уже писал. Но до этого я настоятельно рекомендую вам использовать предложение Blackhole, поскольку оно позволяет решать более широкий набор проблем, которые обычно вызывают плохая архитектура. Пожалуйста, сначала используйте его подход и вернитесь к текущему, если вы знаете, что вы делаете.
Итак, вот:
Вы можете использовать службу $injector
и вводить требуемые определения во время выполнения, что является законным с технической точки зрения, но в соответствии с this post (трудно представить, что это написано в 2008 году), это похоже на черную магию, сделайте это, и она поразит вас:
Application.factory('Blog', ['$injector', function ($injector) {
var Tag = $injector.get('Tag'); // Here is your tag
...
}]);
Application.factory('Tag', ['Blog', function (Blog) {
...
}]);
Изменить
Оказалось, что текущий подход является примером шаблона Locator Service, который является IoC Antipattern.
Ответ 3
ПОСЛЕДНИЙ КУРОРТ: НЕ ОБЕСПЕЧЕН
В моем случае лучший способ обойти проблему с круговой зависимостью, подобную этой в angular, - это вызвать функции-вызовы через $rootScope
-broadcasts. Затем другая служба может прослушивать эту трансляцию и реагировать на желаемый вызов функции. Это может быть не самое элегантное решение, но в некоторых случаях, когда взаимодействие между службами в основном однонаправлено, это может быть разумной альтернативой. (обратите внимание, что это также позволяет возвращать возвращаемые значения обратно в функцию вещания только через обратные вызовы)
Псевдо-пример этого:
angular.module('myApp').factory('service1', ["$rootScope",
function($rootScope) {
function func1() {
// do something
}
$rootScope.$broadcast("callFunc2"); // calls func2 from service 1
return {
func1: func1
}
}
]);
angular.module('myApp').factory('service2', ["service1", "$rootScope",
function(service1, $rootScope) {
function func2() {
// do something
}
service1.func1(); // calls func1 from service 2
$rootScope.on("callFunc2", func2);
}
]);