I am attempting to write two versions of the same program:
I imagine it's not entirely disimilar to how an IDE might implement a normal/debug mode.
My requirements, in decreasing order of importance, are as follows:
I understand requirement 6 may be impossible, since requirement 2 requires access to a classes implementation details (for cases where a public function calls another public function).
For the sake of discussion, consider the following performant version of a program to tell a simple story.
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();
}
}
A naive implementation with mirrors such as the following:
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);
}
I love the elegance of this solution, since I can change the interface of the performant version and this just works. Unfortunately, it fails to satisfy requirement 2, since the inner calls of tellStory() are not wrapped.
A simple though more verbose solution exists:
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);
}
This code can easily be auto-generated using mirrors, but it can result in a large increase in the code-base size, particularly if the performant version has an extensive class hierarchy and I want to have a const analogue to const variables of a class deep in the class tree.
Also, if any class doesn't have a public constructor, this approach prevents the separation of the packages (I think).
I've also considered wrapping all methods of the base class with a wrap method, with the performant version having a trivial wrap function. However, I'm worried this will adversely effect the performant version's performance, particularly if the wrap method was to require, say, an invocation as an input. I also dislike the fact that this intrinsicly links my performant version to the slow version. In my head, I'm thinking there must be a way to make the slower version an extension of the performant version, rather than both versions being an extension of some more general super-version.
Am I missing something really obvious? Is there an in-built 'anySuchMethod' or some such? I'm hoping to combine the elegance of the proxy solution with the completeness of the verbose solution.
You could try to put the additional debugging code inside asserts(...). This gets automatically removed when not run in checked mode. See also
Otherwise just make a global constant (const bool isSlow = true/false;
) Use interfaces everywhere and factory constructors which return the slow or the fast implementation of an interface depending on the isSlow
value.
The slow version can just extend the fast version to reuse its functionality and extend it by overriding its methods.
This way you don't have to use mirrors which causes code bloat, at least for client side code.
When you build all unnecessary code is removed by tree-shaking, depending on the setting of isSlow
.
Using dependency injection helps simplify this way of developing different implementations.