1

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

  • 高性能版本;和
  • 一个较慢的版本,让用户知道发生了什么。

我想这与 IDE 实现正常/调试模式的方式并不完全不同。

我的要求按重要性降序排列如下:

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

我理解要求 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);
}

这段代码可以很容易地使用镜像自动生成,但它可能会导致代码库大小的大幅增加,特别是如果高性能版本具有广泛的类层次结构并且我希望有一个类的 const 变量的 const 模拟在类树的深处。

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

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

我错过了一些非常明显的东西吗?是否有内置的“anySuchMethod”或类似的?我希望将代理解决方案的优雅与详细解决方案的完整性结合起来。

4

1 回答 1

1

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.

于 2015-04-06T08:15:52.193 回答