RoG
RoG

Reputation: 421

Conditional inheritance: base class depend on environment variable

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

Answers (5)

Scott Hannen
Scott Hannen

Reputation: 29252

What not to do:

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.

What to do:

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.

How to make it easier:

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

nzrytmn
nzrytmn

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

Hamed Hajiloo
Hamed Hajiloo

Reputation: 1038

If you want to work with inheritance I think your problem will be solved if you use the Generic Constraints

Upvotes: -1

Krzysztof
Krzysztof

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

Steven Lemmens
Steven Lemmens

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:

  1. Create an interface for your Validations and ValidationsWithStorage class
  2. Check your environment variable
  3. Pass the correct class into the TestsValidation constructor according to the environment variable

Does that help?

Upvotes: 3

Related Questions