dart:包装所有函数调用

dart: wrap all function calls

我正在尝试编写同一程序的两个版本:

我想这与 IDE 可能实现 normal/debug 模式的方式并不完全不同。

我的要求按重要性从高到低排列如下:

  1. 慢速版本应产生与高性能版本相同的结果;
  2. 慢速版本应包装高性能版本进行的 public 函数调用的子集;
  3. 对较慢版本的要求不应对高性能版本的性能产生不利影响;
  4. 最好不要复制代码,但在必要时自动复制;
  5. 代码库大小增加最少;和
  6. 理想情况下,慢速版本应该能够单独打包(大概是对高性能版本的单向依赖)

我理解要求 6 可能是不可能的,因为要求 2 需要访问 classes 实现细节(对于 public 函数调用另一个 public 函数的情况)。

为了便于讨论,请考虑以下程序的高性能版本来讲述一个简单的故事。

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);
}

此代码可以使用镜像轻松自动生成,但会导致代码库大小大幅增加,特别是如果高性能版本具有广泛的 class 层次结构并且我想拥有class 树深处 class 的 const 变量的 const 模拟。

此外,如果任何 class 没有 public 构造函数,这种方法会阻止包的分离(我认为)。

我还考虑过使用 wrap 方法包装基础 class 的所有方法,高性能版本具有简单的包装功能。但是,我担心这会对高性能版本的性能产生不利影响,特别是如果 wrap 方法需要调用作为输入。我也不喜欢这样一个事实,即这从本质上将我的高性能版本与慢速版本联系起来。在我看来,我认为必须有一种方法可以使较慢的版本成为高性能版本的扩展,而不是两个版本都成为一些更通用的超级版本的扩展。

我是否遗漏了一些非常明显的东西?是否有内置的 'anySuchMethod' 或类似的东西?我希望将代理解决方案的优雅与冗长解决方案的完整性结合起来。

您可以尝试将额外的调试代码放在 asserts(...) 中。在检查模式下不 运行 时,这会自动删除。另见

  • Is there a compiler preprocessor in Dart?
  • How to exclude debug code
  • How to achieve precompiler directive like functionality

否则只需创建一个全局常量 (const bool isSlow = true/false;) 随处使用接口和工厂构造函数,return 根据 isSlow 值实现接口的慢速或快速实现。 慢速版本可以只扩展快速版本以重用其功能并通过覆盖其方法来扩展它。
这样您就不必使用导致代码膨胀的镜像,至少对于客户端代码而言。
当你构建时,所有不必要的代码都会通过 tree-shaking 删除,具体取决于 isSlow 的设置。 使用依赖注入有助于简化这种开发不同实现的方式。