Reputation: 421
I have two abstracts classes, 'ValidationsWithStorage' inherits 'Validations'
public abstract class Validations {
// methods..
}
public abstract class ValidationsWithStorage : Validations {
// ...
}
I also have a class:
public abstract class TestsValidations : T
T should be depend on the environment variable:
Environment.GetEnvironmentVariable("useStorage")
If this variable is null I want that T will be Validations. Else, I want that T will be ValidationsWithStorage.
What is the best way to do it?
Thanks
Upvotes: 1
Views: 2092
Reputation: 29252
I don't recommend conditionally changing the definition of a class. There are weird, one-off reasons to do that, but we rarely encounter them and shouldn't make them a normal part of how we write code.
I also don't recommend a factory. A factory implies that you're making a decision at runtime, in production, whether to use a "real" class or a test class. A factory only makes sense if some data available only at runtime determines which implementation you want to use. For example, if you want to validate an address, you might use its country to determine whether to us a US validator, Canadian validator, etc, like this:
var validator = _validatorFactory.GetValidator(address.Country);
Also, that means that the "test" class would be referenced from your production code. That's undesirable and a little strange.
If you aren't making such a decision at runtime then this should be determined in the composition root - that is, in the part of our application that determines, at startup, which classes we're going to use.
To start with, you need an abstraction. This is most often an interface, like this:
public interface IValidator
{
ValidationResult Validate(Something value);
}
The class that needs the validation would look like this:
public class ClassThatNeedsValidation
{
private readonly IValidator _validator;
public ClassThatNeedsValidation(IValidator validator)
{
_validator = validator;
}
// now the method that needs to use validation can
// use _validator.
}
That's dependency injection. ClassThatNeedsValidation
isn't responsible for creating an instance of a validator. That would force it to "know" about the implementation of IValidator
. Instead, it expects to have an IValidator
provided to it. (In other words its dependency - the thing it needs - is injected into it.)
Now, if you're creating an instance of ClassThatNeedsValidation
, it might look like this:
var foo = new ClassThatNeedsValidation(new ValidationWithStorage());
Then, in your unit test project, you might have a test implementation of IValidator
. (You can also use a framework like Moq, but I'm with you - sometimes I prefer to write a test double - a test class that implements the interface.)
So in a unit test, you might write this:
var foo = new ClassThatNeedsValidation(new TestValidator());
This also means that TestValidator
can be in your test project, not mixed with your production code.
In this example:
var foo = new ClassThatNeedsValidation(new ValidationWithStorage());
You can see how this might get messy. What if ValidationWithStorage
has its own dependencies? Then you might have to start writing code like this:
var foo = new ClassThatNeedsValidation(
new ValidationWithStorage(
connectionString,
new SomethingElse(
new Whatever())));
That's not fun. That's why we often use an IoC container, a.k.a dependency injection container.
This is familiar if we use ASP.NET Core, although it's important to know that we don't have to use ASP.NET Core to do this. We can add Microsoft.Extensions.DependencyInjection, Autofac, Windsor, or others to a project.
Explaining this is somewhat beyond the scope of this answer, and it might be more than what you need right now. But it enables us to write code that looks like this:
services.AddSingleton<IValidator, ValidationWithStorage>();
services.AddSingleton<Whatever>();
services.AddSingleton<ISomethingElse, SomethingElse>();
services.AddSingleton<ClassThatNeedsValidation>();
Now, if the container needs to create an instance of ClassThatNeedsValidation
, it will look at the constructor, figure out what dependencies it needs, and create them. If those classes have dependencies it creates them too, and so on.
This takes a minute or several or some reading/trying if it's a new concept, but trust me, it makes writing code and unit tests much easier. (Unless we do it wrong, then it makes everything harder, but that's true of everything.)
What if, for some reason, you wanted to use a different implementation of IValidator
in a different environment? Because the code above is executed once, at startup, that's easy:
if(someVariable = false)
services.AddSingleton<IValidator, OtherValidator>();
else
services.AddSingleton<IValidator, ValidationWithStorage>();
You're making the decision, but you're making it once. A class that depends on IValidator
doesn't need to know about this decision. It doesn't need to ask which environment it's in. If we go down that route, we'll end up with stuff like that polluting all of our classes. It will also make our unit tests much more difficult to write and understand. Making decisions like this at startup - the composition root - eliminates all of that messiness.
Upvotes: 0
Reputation: 6961
I am not sure you can do this with inheritance. This is not the logic of inheritance. It will be better if you use something like factory pattern and change your current deisgn.
May be you can do something like this. I didn't test but i think it will be easier like this:
public interface Validations
{
void ValidationsStuff();
}
public class ValidationsWithStorage : Validations
{
public void ValidationsStuff()
{
//do something
}
}
public class TestsValidations : Validations
{
public void ValidationsStuff()
{
//do something
}
}
public class ValidationsFactory
{
public Validations geValidationsComponent(string useStorage)
{
if (string.IsNullOrEmpty(useStorage))
return new ValidationsWithStorage();
else
return new TestsValidations();
}
}
Upvotes: 4
Reputation: 1038
If you want to work with inheritance I think your problem will be solved if you use the Generic Constraints
Upvotes: -1
Reputation: 16150
You can do that using conditional compilation:
public abstract class TestsValidations
#if USESTORAGE
: ValidationsWithStorage
#else
: Validations
#endif
{
}
You can set it in project configuration or by passing additional parameters to msbuild: /p:DefineConstants="USESTORAGE"
I don't think this is good design, but it is doable.
Upvotes: 2
Reputation: 720
I don't think you can do what you want to do in the way you do it.
Why not let your class TestValidations
take a parameter in its constructor of either type Validations
or ValidationsWithStorage
. If they both follow the same interface, your TestsValidations
class wouldn't need to know (or care) which of the two it's working with.
So basically:
Validations
and ValidationsWithStorage
classTestsValidation
constructor according to the environment variableDoes that help?
Upvotes: 3