长模式匹配链的更好解决方案(针对 Dart)

Better solutions to long pattern matching chains (for Dart)

我经常发现自己不得不使用烦人的模式,例如:

ClassA extends BaseClass {
  static bool is(val) { ... }
}
ClassB extends BaseClass {
  static bool is(val) { ... }
}
...
ClassZ extends BaseClass {
  static bool is(val) { ... }
}

BaseClass parser(val) {
  if(ClassA.is(val)) {
    return ClassA(val);
  } else if(ClassB.is(val)) {
    return ClassB(val);
  ...
  } else if(ClassZ.is(val)) {
    return ClassB(val);
  }
}

这很容易出错,需要大量单调的代码。 我想知道是否有一种方法可以以非特定语言(或特定于 Dart 的语言)的方式加快此过程,而不涉及在定义所有模式匹配器后列出它们。我想避免这种情况,因为由于忘记列出已经定义的 class 的模式匹配器之一,我有太多的错误无法计数。

如果你想减少BaseClass.parser()中的任意条件,你可以使用如下映射:

typedef Specification = bool Function(dynamic val);
typedef Factory = BaseClass Function(dynamic val);

class BaseClass
{
  
  static final Map<Specification, Factory> _factoryMap = {
    (val) => val == 'Hello': (val) => ClassA(),
    (val) => val == 'There': (val) => ClassB(),
  };
  
  static BaseClass? parse(dynamic val)
  {
    for(var key in _factoryMap.keys)
    {
      if(key(val)) return _factoryMap[key]!.call(val);
    }
    throw ArgumentError('No valid factory found!');
  }
  
}

class ClassA extends BaseClass
{ }

class ClassB extends BaseClass
{ }

class ClassC extends BaseClass
{ }

在Python中,可以扩展type,注册子类自己的规范方法,不用像这样手动列出。但是我暂时还不知道 Dart 中有这样的运行时元编程。也许你可以在 Dart 中使用 source_gen 来自动生成条件。

将检查与对象的构造相结合会略有帮助。这将减少意外使用错误检查和构造函数的可能性,并且将减少您需要在 class 定义之外添加的代码量:

ClassA extends BaseClass {
  /// Attempts to return a [ClassA] if possible.
  ///
  /// Returns `null` if inappropriate.
  static ClassA? tryFrom(dynamic val) { ... }
}

ClassB extends BaseClass {
  static ClassB? tryFrom(dynamic val) { ... }
}

...

ClassZ extends BaseClass {
  static ClassZ? tryFrom(dynamic val) { ... }
}

BaseClass parser(dynamic val) {
  BaseClass object = ClassA.tryFrom(val) ?? 
      ClassB.tryFrom(val) ??
      ... ??
      ClassZ.tryFrom(val);
  if (object == null) {
    // Throw some exception here.
  }

  return object;
}

从那里,您可以让 parser 使用循环:

typedef TryFromFunction = BaseClass? Function(dynamic);

final tryFromFunctions = [
  ClassA.tryFrom,
  ClassB.tryFrom,
  ...
  ClassZ.tryFrom,
];

BaseClass parser(dynamic val) {
  for (var tryFrom in tryFromFunctions) {
    var object = tryFrom(val);
    if (object != null) {
      return object;
    }
  }
  // Throw some exception here.
}

这并不能免除您在添加新 class 时更新其他位置的责任,但是:

  • 工作量很小。
  • 也许 可以添加单元测试来检查列表是否已更新。例如,如果 ClassAClassB、...、ClassZ 中的每一个都在一个单独的文件中,可以通过路径或文件名轻松区分,那么您可以进行一个测试来验证 tryFromFunctions.length 匹配这些文件的数量。

目前,我使用的解决方案取自所列的其他解决方案并有所不同。基本上,我像其他人一样创建了一个匹配器列表,但在 class 本身中添加了匹配器。基本上,

List<BaseClass Function(dynamic)> _matchers = [];

bool addMatcher(bool Function(dynamic) matcher, BaseClass Function(dynamic) factory) {
  _matchers.add((val) {
    return matcher(val) ? factory(val) : null;
  });
  return true;
}

class ClassA extends BaseClass {
  static final _ = addMatcher(matches, (val) => ClassA(val));
  
  ClassA(val) { ... }

  static bool matches(val) { ... }
}

class ClassB extends BaseClass {
   static final _ = addMatcher(...);
   ...
}

...

BaseClass parser(val) {
  for (var matcher in _matchers) {
    var object = matcher(val);
    if (object != null) {
      return object;
    }
  }
  // Throw some exception here.
}

这背后的原因是我可以很容易地验证 class 正在被检查。不幸的是,我仍然不确定如何自动执行此操作,因为这只是一个快速而肮脏的解决方案。如果我最终实现了自动 generation/verification.

的源代码生成或单元测试,我可能会通过更新回到这里。