To recap from last time, we want to create a simple library that allows us to express validation in a straightforward way, allowing us to concentrate on the rules we’re checking, not the boilerplate needed to make it work.
We’ll start by declaring an abstract class,
ValidationResult to represent the results of validation:
Why an abstract class instead of an interface? Because there are things you can do with an abstract class that you can’t do with an interface. More on that in a later post in this series.
The private protected constructor is there because we’re not creating a general purpose ancestor class; we’re going to create a small number of specialised descendant classes, each for a specific purpose. With this visibility (new in C# 7.2), we can restrict visibility to descendents in the same assembly only. This helps us to ensure that only our planned descendants are present.
In earlier versions of C#, there are two ways to achieve similar results. We could declare the constructor as internal, limiting visibility to the assembly, or we could make it private and nest all of the descendents within
Our first two descendents are to capture two basic results of validation - success and error.
ErrorResult contains a single error message, the
SuccessResult is even simpler than that.
To make it easier to create these - and to create a more declarative style of API - let’s create a static class that acts as a factory:
Notice that we create a static reference instance of
SuccessResult that we reuse whenever needed. Success results are boring - they don’t have any unique information, so we don’t need to use a different one each time - and reuse helps to keep the memory footprint down.
If you look back at the declaration for
ValidationResult at the top of the file, you’ll see that it represents one or more messages that result from a validation check - but our current implementations only capture a single result.
To handle the aggregation of multiple results, we’ll now create
To finish off this post, here are two puzzles for you to consider.
The constructor for
AggregateResult is internal and takes a
ImmutableHashSet<ValidationResult> parameter. Clearly, I’m not intending that consumers do the aggregation themselves. How do you think our consumers will end up with instances of
AggregateResult if they’re not calling that constructor? The use of
ImmutableHashSet<T> is a clue.
All of the factory methods on
Validation return a
ValidationResult - and so do all of the sample methods that I presented in the earlier post. We don’t want our consumers to have to test each instance to work out the type - that’s a long way from the level of convenience we’d like to achieve. How would you modify these classes to make it easy to react to the outcome of validation?