条件继承:基类取决于环境变量

时间:2019-08-20 07:45:04

标签: c# inheritance abstract-class

我有两个抽象类,'ValidationsWithStorage'继承了'Validations'

   public abstract class Validations {
    // methods..
   }

   public abstract class ValidationsWithStorage : Validations { 
    // ... 
    }

我也有一个课程:

public abstract class TestsValidations : T

T应取决于环境变量:

Environment.GetEnvironmentVariable("useStorage") 

如果此变量为null,我希望T为Validations。 否则,我希望T为ValidationsWithStorage。

最好的方法是什么?

谢谢

5 个答案:

答案 0 :(得分:3)

我认为您无法以自己的方式做自己想做的事情。

为什么不让类TestValidations在其构造函数中使用ValidationsValidationsWithStorage类型的参数。如果它们都遵循相同的界面,则您的TestsValidations类将不需要知道(或关心)它正在使用哪两个。

所以基本上:

  1. 为您的ValidationsValidationsWithStorage类创建一个界面
  2. 检查环境变量
  3. 根据环境变量将正确的类传递到TestsValidation构造函数中

有帮助吗?

答案 1 :(得分:3)

我不确定您可以通过继承来做到这一点。这不是继承的逻辑。如果使用工厂模式之类的东西并更改当前设计,效果会更好。

也许您可以做这样的事情。我没有测试,但我认为这样会更容易:

    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();
    }
}

答案 2 :(得分:1)

您可以使用条件编译来做到这一点:

public abstract class TestsValidations
#if USESTORAGE
    : ValidationsWithStorage
#else
    : Validations
#endif
{

}

您可以在项目配置中设置它,也可以通过将其他参数传递给msbuild来设置:/p:DefineConstants="USESTORAGE"

我认为这不是很好的设计,但这是可行的。

答案 3 :(得分:1)

如果您想使用继承,我认为如果您使用Generic Constraints

,您的问题将得到解决。

答案 4 :(得分:0)

该怎么办

我不建议有条件地更改类的定义。这样做有很多奇怪的一次性原因,但是我们很少遇到它们,因此不应该使它们成为我们编写代码的正常部分。

我也不推荐工厂。工厂意味着您要在运行时生产中做出决定,是使用“真实”类还是使用测试类。仅当某些仅在运行时可用的数据确定要使用的实现时,工厂才有意义。例如,如果您要验证地址,则可以使用其国家/地区来确定是否向我们提供美国验证者,加拿大验证者等,

var validator = _validatorFactory.GetValidator(address.Country);

此外,这意味着将在您的生产代码中引用“ test”类。这是不可取的,有点奇怪。

怎么做:

如果您在运行时没有做出这样的决定,那么应该在组合根目录中确定-也就是说,在应用程序的一部分中,即在启动时确定我们将使用哪些类。

首先,您需要一个抽象。这通常是一个界面,例如:

public interface IValidator
{
    ValidationResult Validate(Something value);
}

需要验证的类如下所示:

public class ClassThatNeedsValidation
{
    private readonly IValidator _validator;

    public ClassThatNeedsValidation(IValidator validator)
    {
        _validator = validator;
    }

    // now the method that needs to use validation can
    // use _validator.
}

那是依赖注入。 ClassThatNeedsValidation不负责创建验证程序的实例。这将迫使它“了解” IValidator的实现。相反,它期望提供一个IValidator。 (换句话说,将其依赖项(它需要的东西)注入了。)

现在,如果您正在创建ClassThatNeedsValidation的实例,它可能看起来像这样:

var foo = new ClassThatNeedsValidation(new ValidationWithStorage());

然后,在您的单元测试项目中,您可能具有IValidator的测试实现。 (您也可以使用Moq之类的框架,但我支持您-有时我更喜欢编写一个double测试-实现接口的测试类。)

因此,在单元测试中,您可以这样写:

var foo = new ClassThatNeedsValidation(new TestValidator());

这也意味着TestValidator可以在您的测试项目中,而不与生产代码混合。

如何使其更容易:

在此示例中:

var foo = new ClassThatNeedsValidation(new ValidationWithStorage());

您可以看到它如何变得凌乱。如果ValidationWithStorage有自己的依存关系怎么办?然后,您可能必须开始编写如下代码:

var foo = new ClassThatNeedsValidation(
    new ValidationWithStorage(
        connectionString, 
        new SomethingElse(
            new Whatever())));

那不好玩。这就是为什么我们经常使用IoC容器(也称为依赖项注入容器)的原因。

如果我们使用ASP.NET Core,这是很熟悉的,尽管重要的是我们不必使用ASP.NET Core。我们可以将Microsoft.Extensions.DependencyInjectionAutofac,Windsor或其他添加到项目中。

对此进行解释在某种程度上超出了此答案的范围,并且可能超出您现在所需要的范围。但这使我们能够编写如下代码:

services.AddSingleton<IValidator, ValidationWithStorage>();
services.AddSingleton<Whatever>();
services.AddSingleton<ISomethingElse, SomethingElse>();
services.AddSingleton<ClassThatNeedsValidation>();

现在,如果容器需要创建ClassThatNeedsValidation的实例,它将查看构造函数,找出所需的依赖项并创建它们。如果这些类具有依赖关系,它也会创建依赖关系,依此类推。

如果这是一个新概念,则需要花费一分钟或数分钟,或者需要一些阅读/尝试,但是请相信我,它使编写代码和单元测试变得更加容易。 (除非我们做错了,否则它将使所有事情变得更加艰难,但这对所有事情都是如此。)

如果由于某种原因您想在不同的环境中使用IValidator的不同实现怎么办?因为上面的代码执行一次,所以在启动时很简单:

if(someVariable = false)
    services.AddSingleton<IValidator, OtherValidator>();
else
    services.AddSingleton<IValidator, ValidationWithStorage>();

您正在做出决定,但您正在做出一次。依赖IValidator的类无需了解此决定。不需要问它在哪个环境中。如果我们沿着那条路线走,我们最终会得到类似污染所有类的东西。这也将使我们的单元测试更加难以编写和理解。在启动时做出这样的决定-合成的根本-消除了所有麻烦。