Дротик: обернуть все вызовы функций
Я пытаюсь написать две версии одной и той же программы:
- версия исполнителя; а также
- более медленная версия, которая позволяет пользователю узнать, что происходит.
Я предполагаю, что это не совсем отличается от того, как в среде IDE может быть реализован обычный режим / режим отладки.
Мои требования в порядке убывания важности следующие:
- медленная версия должна давать те же результаты, что и версия-исполнитель;
- медленная версия должна обернуть подмножество вызовов открытых функций, выполненных версией-исполнителем;
- требование более медленной версии не должно отрицательно влиять на производительность исходной версии;
- желательно не воспроизводить код, но при необходимости автоматически воспроизводить;
- минимальное увеличение размера базы кода; а также
- в идеале медленная версия должна быть в состоянии упаковываться отдельно (предположительно, с односторонней зависимостью от производительной версии)
Я понимаю, что требование 6 может быть невозможным, поскольку требование 2 требует доступа к деталям реализации классов (для случаев, когда открытая функция вызывает другую открытую функцию).
Для обсуждения рассмотрим следующую версию программы, чтобы рассказать простую историю.
class StoryTeller{
void tellBeginning() => print('This story involves many characters.');
void tellMiddle() => print('After a while, the plot thickens.');
void tellEnd() => print('The characters resolve their issues.');
void tellStory(){
tellBeginning();
tellMiddle();
tellEnd();
}
}
Наивная реализация с зеркалами, такими как следующее:
class Wrapper{
_wrap(Function f, Symbol s){
var name = MirrorSystem.getName(s);
print('Entering $name');
var result = f();
print('Leaving $name');
return result;
}
}
@proxy
class StoryTellerProxy extends Wrapper implements StoryTeller{
final InstanceMirror mirror;
StoryTellerProxy(StoryTeller storyTeller): mirror = reflect(storyTeller);
@override
noSuchMethod(Invocation invocation) =>
_wrap(() => mirror.delegate(invocation), invocation.memberName);
}
Мне нравится элегантность этого решения, так как я могу изменить интерфейс производительной версии, и это просто работает. К сожалению, он не удовлетворяет требованию 2, поскольку внутренние вызовы tellStory() не упакованы.
Существует простое, но более подробное решение:
class StoryTellerVerbose extends StoryTeller with Wrapper{
void tellBeginning() => _wrap(() => super.tellBeginning(), #tellBeginning);
void tellMiddle() => _wrap(() => super.tellMiddle(), #tellMiddle);
void tellEnd() => _wrap(() => super.tellEnd(), #tellEnd);
void tellStory() => _wrap(() => super.tellStory(), #tellStory);
}
Этот код может быть легко сгенерирован автоматически с использованием зеркал, но это может привести к значительному увеличению размера базы кода, особенно если версия-исполнитель имеет обширную иерархию классов, и я хочу иметь константный аналог константным переменным класса глубоко в дереве классов.
Кроме того, если какой-либо класс не имеет публичного конструктора, этот подход предотвращает разделение пакетов (я думаю).
Я также рассмотрел обтекание всех методов базового класса методом обтекания, при этом версия-исполнитель имеет тривиальную функцию обтекания. Тем не менее, я обеспокоен тем, что это отрицательно скажется на производительности производительной версии, особенно если метод обтекания потребовал, скажем, вызова в качестве входных данных. Мне также не нравится тот факт, что это по своей сути связывает мою версию с медленной версией. В моей голове, я думаю, должен быть способ сделать более медленную версию расширением производительной версии, вместо того, чтобы обе версии были расширением более общей супер-версии.
Я что-то упускаю действительно очевидное? Существует ли встроенный метод anySuchMethod или что-то подобное? Я надеюсь объединить элегантность прокси-решения с полнотой подробного решения.
1 ответ
Вы можете попытаться поместить дополнительный код отладки в утверждения (...). Это автоматически удаляется, когда не запускается в проверенном режиме. Смотрите также
- Есть ли в Dart препроцессор компилятора?
- Как исключить отладочный код
- Как добиться директивы прекомпилятора как функциональность
В противном случае просто сделайте глобальную константу (const bool isSlow = true/false;
) Используйте интерфейсы везде и фабричные конструкторы, которые возвращают медленную или быструю реализацию интерфейса в зависимости от isSlow
значение. Медленная версия может просто расширить быструю версию, чтобы повторно использовать ее функциональность и расширить ее, переопределив ее методы.
Таким образом, вам не нужно использовать зеркала, которые вызывают раздувание кода, по крайней мере, для кода на стороне клиента.
При сборке весь ненужный код удаляется путем встряхивания дерева, в зависимости от настройки isSlow
, Использование внедрения зависимостей помогает упростить этот способ разработки различных реализаций.