条件继承:base class 依赖于环境变量
Conditional inheritance: base class depend on environment variable
我有两个摘要classes,'ValidationsWithStorage'继承'Validations'
public abstract class Validations {
// methods..
}
public abstract class ValidationsWithStorage : Validations {
// ...
}
我也有一个class:
public abstract class TestsValidations : T
T 应该取决于环境变量:
Environment.GetEnvironmentVariable("useStorage")
如果此变量为空,我希望 T 将是验证。
否则,我希望 T 将是 ValidationsWithStorage。
最好的方法是什么?
谢谢
我不认为你可以用你的方式做你想做的事。
为什么不让您的 class TestValidations
在其构造函数中使用 Validations
或 ValidationsWithStorage
类型的参数。如果它们都遵循相同的界面,您的 TestsValidations
class 将不需要知道(或关心)它正在使用的是两者中的哪一个。
所以基本上:
- 为您的
Validations
和 ValidationsWithStorage
class 创建一个界面
- 检查你的环境变量
- 根据环境变量将正确的class传入
TestsValidation
构造函数
有帮助吗?
我不确定您是否可以通过继承来做到这一点。这不是继承的逻辑。如果你使用类似工厂模式的东西并改变你当前的设计会更好。
也许你可以做这样的事情。我没有测试,但我认为这样会更容易:
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();
}
}
您可以使用条件编译来做到这一点:
public abstract class TestsValidations
#if USESTORAGE
: ValidationsWithStorage
#else
: Validations
#endif
{
}
您可以在项目配置中设置它或通过将附加参数传递给 msbuild:/p:DefineConstants="USESTORAGE"
我不认为这是好的设计,但它是可行的。
如果你想使用继承,我认为如果你使用 Generic Constraints
,你的问题就会得到解决
不该做什么:
我不建议有条件地更改 class 的定义。这样做有一些奇怪的、一次性的原因,但我们很少遇到它们,不应该让它们成为我们编写代码的正常部分。
我也不推荐工厂。工厂意味着您在运行时做出决定,在生产中,是使用“真实”class 还是测试class。仅当某些仅在运行时可用的数据决定了您要使用哪个实现时,工厂才有意义。例如,如果你想验证一个地址,你可能会使用它的国家来确定我们是否使用美国验证器、加拿大验证器等,就像这样:
var validator = _validatorFactory.GetValidator(address.Country);
此外,这意味着“测试”class 将从您的生产代码中引用。这是不可取的,而且有点奇怪。
要做什么:
如果您不在运行时做出这样的决定,那么这应该在组合根中确定 - 也就是说,在我们的应用程序的一部分中确定,在启动时,我们 classes重新使用。
首先,您需要一个抽象。这通常是一个界面,如下所示:
public interface IValidator
{
ValidationResult Validate(Something value);
}
需要验证的class看起来像这样:
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 这样的框架,但我支持您 - 有时我更喜欢编写一个测试替身 - 一个实现接口的测试 class。)
所以在单元测试中,你可以这样写:
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 容器,a.k.a 依赖注入容器。
如果我们使用 ASP.NET 核心,这很熟悉,但重要的是要知道我们不必使用 ASP.NET 核心来执行此操作。我们可以将 Microsoft.Extensions.DependencyInjection, Autofac、Windsor 或其他项目添加到项目中。
解释这在某种程度上超出了这个答案的范围,它可能比你现在需要的更多。但它使我们能够编写如下代码:
services.AddSingleton<IValidator, ValidationWithStorage>();
services.AddSingleton<Whatever>();
services.AddSingleton<ISomethingElse, SomethingElse>();
services.AddSingleton<ClassThatNeedsValidation>();
现在,如果容器需要创建 ClassThatNeedsValidation
的实例,它将查看构造函数,找出它需要的依赖项,然后创建它们。如果那些 classes 有依赖关系,它也会创建它们,依此类推。
如果这是一个新概念,这需要一分钟或几分钟或更多 reading/trying,但请相信我,它使编写代码和单元测试变得容易得多。 (除非我们做错了,否则一切都会变得更难,但凡事都是如此。)
如果出于某种原因,您想在不同的环境中使用 IValidator
的不同实现怎么办?因为上面的代码只执行一次,在启动的时候,很简单:
if(someVariable = false)
services.AddSingleton<IValidator, OtherValidator>();
else
services.AddSingleton<IValidator, ValidationWithStorage>();
您正在做决定,但您只做 一次。依赖于 IValidator
的 class 不需要知道这个决定。它不需要询问它在哪个环境中。如果我们沿着这条路走下去,我们最终会得到类似污染我们所有 classes 的东西。它还将使我们的单元测试更难编写和理解。在启动时做出这样的决定——组合根——消除了所有的混乱。
我有两个摘要classes,'ValidationsWithStorage'继承'Validations'
public abstract class Validations {
// methods..
}
public abstract class ValidationsWithStorage : Validations {
// ...
}
我也有一个class:
public abstract class TestsValidations : T
T 应该取决于环境变量:
Environment.GetEnvironmentVariable("useStorage")
如果此变量为空,我希望 T 将是验证。 否则,我希望 T 将是 ValidationsWithStorage。
最好的方法是什么?
谢谢
我不认为你可以用你的方式做你想做的事。
为什么不让您的 class TestValidations
在其构造函数中使用 Validations
或 ValidationsWithStorage
类型的参数。如果它们都遵循相同的界面,您的 TestsValidations
class 将不需要知道(或关心)它正在使用的是两者中的哪一个。
所以基本上:
- 为您的
Validations
和ValidationsWithStorage
class 创建一个界面
- 检查你的环境变量
- 根据环境变量将正确的class传入
TestsValidation
构造函数
有帮助吗?
我不确定您是否可以通过继承来做到这一点。这不是继承的逻辑。如果你使用类似工厂模式的东西并改变你当前的设计会更好。
也许你可以做这样的事情。我没有测试,但我认为这样会更容易:
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();
}
}
您可以使用条件编译来做到这一点:
public abstract class TestsValidations
#if USESTORAGE
: ValidationsWithStorage
#else
: Validations
#endif
{
}
您可以在项目配置中设置它或通过将附加参数传递给 msbuild:/p:DefineConstants="USESTORAGE"
我不认为这是好的设计,但它是可行的。
如果你想使用继承,我认为如果你使用 Generic Constraints
,你的问题就会得到解决不该做什么:
我不建议有条件地更改 class 的定义。这样做有一些奇怪的、一次性的原因,但我们很少遇到它们,不应该让它们成为我们编写代码的正常部分。
我也不推荐工厂。工厂意味着您在运行时做出决定,在生产中,是使用“真实”class 还是测试class。仅当某些仅在运行时可用的数据决定了您要使用哪个实现时,工厂才有意义。例如,如果你想验证一个地址,你可能会使用它的国家来确定我们是否使用美国验证器、加拿大验证器等,就像这样:
var validator = _validatorFactory.GetValidator(address.Country);
此外,这意味着“测试”class 将从您的生产代码中引用。这是不可取的,而且有点奇怪。
要做什么:
如果您不在运行时做出这样的决定,那么这应该在组合根中确定 - 也就是说,在我们的应用程序的一部分中确定,在启动时,我们 classes重新使用。
首先,您需要一个抽象。这通常是一个界面,如下所示:
public interface IValidator
{
ValidationResult Validate(Something value);
}
需要验证的class看起来像这样:
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 这样的框架,但我支持您 - 有时我更喜欢编写一个测试替身 - 一个实现接口的测试 class。)
所以在单元测试中,你可以这样写:
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 容器,a.k.a 依赖注入容器。
如果我们使用 ASP.NET 核心,这很熟悉,但重要的是要知道我们不必使用 ASP.NET 核心来执行此操作。我们可以将 Microsoft.Extensions.DependencyInjection, Autofac、Windsor 或其他项目添加到项目中。
解释这在某种程度上超出了这个答案的范围,它可能比你现在需要的更多。但它使我们能够编写如下代码:
services.AddSingleton<IValidator, ValidationWithStorage>();
services.AddSingleton<Whatever>();
services.AddSingleton<ISomethingElse, SomethingElse>();
services.AddSingleton<ClassThatNeedsValidation>();
现在,如果容器需要创建 ClassThatNeedsValidation
的实例,它将查看构造函数,找出它需要的依赖项,然后创建它们。如果那些 classes 有依赖关系,它也会创建它们,依此类推。
如果这是一个新概念,这需要一分钟或几分钟或更多 reading/trying,但请相信我,它使编写代码和单元测试变得容易得多。 (除非我们做错了,否则一切都会变得更难,但凡事都是如此。)
如果出于某种原因,您想在不同的环境中使用 IValidator
的不同实现怎么办?因为上面的代码只执行一次,在启动的时候,很简单:
if(someVariable = false)
services.AddSingleton<IValidator, OtherValidator>();
else
services.AddSingleton<IValidator, ValidationWithStorage>();
您正在做决定,但您只做 一次。依赖于 IValidator
的 class 不需要知道这个决定。它不需要询问它在哪个环境中。如果我们沿着这条路走下去,我们最终会得到类似污染我们所有 classes 的东西。它还将使我们的单元测试更难编写和理解。在启动时做出这样的决定——组合根——消除了所有的混乱。