我有两个抽象类,'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。
最好的方法是什么?
谢谢
答案 0 :(得分:3)
我认为您无法以自己的方式做自己想做的事情。
为什么不让类TestValidations
在其构造函数中使用Validations
或ValidationsWithStorage
类型的参数。如果它们都遵循相同的界面,则您的TestsValidations
类将不需要知道(或关心)它正在使用哪两个。
所以基本上:
Validations
和ValidationsWithStorage
类创建一个界面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.DependencyInjection,Autofac,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
的类无需了解此决定。不需要问它在哪个环境中。如果我们沿着那条路线走,我们最终会得到类似污染所有类的东西。这也将使我们的单元测试更加难以编写和理解。在启动时做出这样的决定-合成的根本-消除了所有麻烦。