Factory to create generic classes
-
10-12-2019 - |
Question
I have an abstract class called Validator:
public abstract class Validator<T> where T : IValidatable
{
public abstract bool Validate(T input);
}
And I have a few concrete implementations. One is AccountValidator:
public class AccountCreateValidator : Validator<IAccount>
{
public override bool Validate(IAccount input)
{
//some validation
}
}
Another would be LoginValidator:
public class LoginValidator : Validator<IAccount>
{
public override bool Validate(IAccount input)
{
//some different validation
}
}
I now want to create a factory to return the an instance of a validator implementation. Something like:
public static class ValidatorFactory
{
public static Validator GetValidator(ValidationType validationType)
{
switch (validationType)
{
case ValidationType.AccountCreate:
return new AccountCreateValidator();
}
}
}
I'd then like to do call it like
Validator myValidator = ValidatorFactory.GetValidator(ValidationType.AccountCreate);
However it doesn't like the return new AccountCreateValidator() line, or the fact I'm declaring myValidator as Validator and not Validator<SomeType>
.
Any help would be appreciated.
Solution
It seems that you are using the factory to translate an enum argument into a concrete validation implementation. But I would imagine that although the caller does not know or care about the concrete type of the validator, it presumably does know the type it wishes it to validate. That should mean that it is reasonable to make the GetValidator method a generic method:
public static Validator<TypeToValidate> GetValidator<TypeToValidate>(ValidationType validationType) where TypeToValidate : IValidatable
Then calling code would look like this:
Validator<IAccount> validator = ValidatorFactory.GetValidator<IAccount>(ValidationType.AccountCreate
)
OTHER TIPS
if you want it to be used like you have said, without specifying the generic parameter then you can declare a non generic interface and make you Validator abstract class implement that. Untested but something along these lines:
public interface IValidator
{
bool Validate(object input);
}
public abstract class Validator<T> : IValidator where T : IValidatable
{
public abstract bool Validate(T input);
public bool Validate (object input)
{
return Validate ((T) input);
}
}
public static class ValidatorFactory
{
public static IValidator GetValidator(ValidationType validationType)
{
switch (validationType)
{
case ValidationType.AccountCreate:
return new AccountCreateValidator();
}
}
}
then this code:
IValidator myValidator = ValidatorFactory.GetValidator(ValidationType.AccountCreate);
Should work ok.
Normally I would have a factory which accepted a Type
as a parameter so that the factory would create a unique derived type. But because you have multiple validators that accept the same kind of object to validate (in this case an IAccount
) I think you'll have to provide the generic parameter to your factory as well as what kind of validator to create, like this:
public static class ValidatorFactory
{
public static Validator<T> GetValidator<T>(ValidationType validationType)
where T : IValidatable
{
switch (validationType)
{
case ValidationType.AccountCreate:
return new AccountCreateValidator() as Validator<T>;
// etc...
}
}
}
I've tried calling:
var value = ValidatorFactory.GetValidator<IAccount>(ValidationType.AccountCreate)
and this now returns an AccountCreateValidator
cast to the correct Validator<IAccount>
type.
It's not exactly ideal as you now need to know what validator you want and what input it accepts, but you should hopefully get a compilation error if passing the wrong input type because of the explicit cast I've added, e.g. ValidatorFactory.GetValidator<IUser>(ValidationType.AccountCreate)
should fail.
EDIT: because of the comment saying this would not compile, I've edited the above code snippet to do the cast as new AccountCreateValidator() as Validator<T>
. I thought it could be cast either way, but apparently not (not sure why though). I have verified this works in LINQPad and I can get a result back from a validator.
I also believe my previous comment about possibly getting a compilation error if you pass in the wrong generic types no longer stands as casting using the as
keyword would just return null
if it was unable to do it.