я тоже ищу хорошее решение проблемы кругового подсчета ссылок.
я воровал позаимствовал API из World of Warcraft, связанный с достижениями. я неявно переводил его в интерфейсы, когда понял, что у меня циклические ссылки.
Примечание. Вы можете заменить слово достижения на приказы, если вам не нравятся достижения. Но кто не любит достижения?
Вот само достижение:
IAchievement = interface(IUnknown)
function GetName: string;
function GetDescription: string;
function GetPoints: Integer;
function GetCompleted: Boolean;
function GetCriteriaCount: Integer;
function GetCriteria(Index: Integer): IAchievementCriteria;
end;
А затем список критериев достижения:
IAchievementCriteria = interface(IUnknown)
function GetDescription: string;
function GetCompleted: Boolean;
function GetQuantity: Integer;
function GetRequiredQuantity: Integer;
end;
Все достижения регистрируются в центральном IAchievementController
:
IAchievementController = interface
{
procedure RegisterAchievement(Achievement: IAchievement);
procedure UnregisterAchievement(Achievement: IAchievement);
}
Затем контроллер можно использовать для получения списка всех достижений:
IAchievementController = interface
{
procedure RegisterAchievement(Achievement: IAchievement);
procedure UnregisterAchievement(Achievement: IAchievement);
function GetAchievementCount(): Integer;
function GetAchievement(Index: Integer): IAchievement;
}
Идея заключалась в том, что если произойдет что-то интересное, система вызовет IAchievementController
и уведомит их о том, что произошло что-то интересное:
IAchievementController = interface
{
...
procedure Notify(eventType: Integer; gParam: TGUID; nParam: Integer);
}
И когда происходит событие, контроллер будет перебирать каждого дочернего элемента и уведомлять их о событии с помощью их собственного метода Notify
:
IAchievement = interface(IUnknown)
function GetName: string;
...
function GetCriteriaCount: Integer;
function GetCriteria(Index: Integer): IAchievementCriteria;
procedure Notify(eventType: Integer; gParam: TGUID; nParam: Integer);
end;
Если объект Achievement
решит, что событие представляет интерес для него, он уведомит свои дочерние критерии:
IAchievementCriteria = interface(IUnknown)
function GetDescription: string;
...
procedure Notify(eventType: Integer; gParam: TGUID; nParam: Integer);
end;
До сих пор график зависимостей всегда располагался сверху вниз:
IAchievementController --> IAchievement --> IAchievementCriteria
Но что происходит, когда критерии достижения соблюдены? Объект Criteria
должен был уведомить своего родителя о достижении:
IAchievementController --> IAchievement --> IAchievementCriteria
^ |
| |
+----------------------+
Это означает, что Criteria
потребуется ссылка на его родителя; кто теперь ссылается друг на друга - утечка памяти.
И когда достижение, наконец, будет выполнено, ему нужно будет уведомить родительский контроллер, чтобы он мог обновлять представления:
IAchievementController --> IAchievement --> IAchievementCriteria
^ | ^ |
| | | |
+----------------------+ +----------------------+
Теперь Controller
и его потомок Achievements
циклически ссылаются друг на друга - больше утечек памяти.
я подумал, что, возможно, объект Criteria
мог бы вместо этого уведомить объект Controller
, удалив ссылку на его родителя. Но у нас все еще есть циклическая ссылка, просто она занимает больше времени:
IAchievementController --> IAchievement --> IAchievementCriteria
^ | |
| | |
+<---------------------+ |
| |
+-------------------------------------------------+
Решение World of Warcraft
Теперь World of Warcraft API не ориентирован на объекты. Но он решает любые циклические ссылки:
Не передавайте ссылки на Controller
. Иметь один глобальный одноэлементный класс Controller
. Таким образом, достижение не должно ссылаться на контроллер, а просто использовать его.
Минусы. Делает тестирование и имитацию невозможными, потому что вам нужно иметь известную глобальную переменную.
Достижение не знает своего списка критериев. Если вы хотите Criteria
для Achievement
, попросите для них Controller
:
IAchievementController = interface(IUnknown)
function GetAchievementCriteriaCount(AchievementGUID: TGUID): Integer;
function GetAchievementCriteria(Index: Integer): IAchievementCriteria;
end;
Минусы. Achievement
больше не может принимать решение о передаче уведомлений своему Criteria
, поскольку у него нет необходимых критериев. Теперь вам нужно зарегистрировать Criteria
в Controller
Когда Criteria
завершается, он уведомляет Controller
, который уведомляет Achievement
:
IAchievementController-->IAchievement IAchievementCriteria
^ |
| |
+----------------------------------------------+
Минусы: голова болит.
я уверен, что метод Teardown
гораздо более желателен, чем перестройка всей системы в ужасно запутанный API.
Но, как вы задаетесь вопросом, возможно, есть лучший способ.
person
Ian Boyd
schedule
05.05.2012