Factory to create generic classes
Asked Answered
J

3

6

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.

Jamnis answered 9/3, 2012 at 10:31 Comment(4)
The code you've got is currently invalid - the end of the method is reachable. Please show a short but complete program which demonstrates the problem.Stall
Would you not be better getting a validator based on the type of IValidatable rather than based on an Enum? Then you could return a Validator<T> and take a T in the GetValidator method and T could be restricted to be of IValidatableMaurizia
class Validator<T> where T : IValidatable, isn't that you just want class Validator : IValidatable?Rexer
Sorry, wasn't clear before. I want to have multiple validators for the same IValidatable. For example AccountCreateValidator and LoginValidator would both validate against an IAccountJamnis
D
2

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)

Detritus answered 9/3, 2012 at 11:1 Comment(4)
+1 this is a better approach, as long as you know what type you are going to validateMaurizia
You're right, it's fair enough to pass in the type we want to validate. Can you please show me how I'd return an instance of a validator? Changed to your code but the compiler isn't happy with my return statement: return new AccountCreateValidator();Jamnis
The problem there is that you are returning a validator which is only valid for a particular type of TypeToValidate. I think you either want to make this factory for a single type of TypeToValidate, ie make it an AcountValidator factory, or if you want to make it generic, make the whole factory generic on TypeToValidate and have a mechanism to register concrete validators against ValidationType values.Detritus
That makes sense. Ok I'll create multiple factories. Thanks for your help on this guysJamnis
M
1

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.

Maurizia answered 9/3, 2012 at 10:49 Comment(2)
Looking better, however compiler complains that Validator doesn't implement IValidator.Validate(object input)Jamnis
I updated the answer with a solution to that, unfortunately once you give up having the generic parameter defined everywhere then you have to deal with what happens if you are called with the wrong type of argument... I suppose your solution will depend on if you know what the validation type is at the call site. If you do then Foo42's solution is better as it retains the strong typing. if you don't then you might have to compromise with a solution like thisMaurizia
P
0

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.

Perusse answered 9/3, 2012 at 11:18 Comment(1)
Thanks but it gives me the following error: Cannot convert type 'AccountCreateValidator' to 'Validator<T>'Jamnis

© 2022 - 2024 — McMap. All rights reserved.