I've often found myself having to use annoying patterns like:
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);
}
}
This is very error prone and requires a lot of monotonous code. I was wondering if there was a way to expedite this process in a non-language specific (or in a language specific for Dart) way that doesn't involve listing all the pattern matchers after they've been defined. I would like to avoid this as I had too many bugs to count caused by forgetting to list one of the already defined class's pattern matcher.
CodePudding user response:
If you want to cut down the arbitrary conditionals in the BaseClass.parser(), you can use a map as follows:
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
{ }
In Python, you can extend type and register the subclass's own specification method without manually listing like this. But I am not aware of such runtime meta programming in Dart for the time being. Maybe you can use source_gen in Dart to generate the conditionals automatically.
CodePudding user response:
Combining the check with the construction of the object would help slightly. That would reduce the potential of accidentally using the wrong check and constructor, and it would reduce the amount of code you'd need to add outside of the class definitions:
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;
}
from there, you can make parser use a loop:
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.
}
That wouldn't absolve you of the responsibility of updating some other location whenever a new class is added, but:
- The work would be minimal.
- You maybe could add unit tests to check that the list is updated. For example, if each of
ClassA,ClassB, ...,ClassZis in a separate file easily distinguished by path or filename, then you could have a test that verifies thattryFromFunctions.lengthmatches the number of those files.
