Let's assume that our system can perform actions, and that an action requires some parameters to do its work. I have defined the following base class for all actions (simplified for your reading pleasure):
假设我们的系统可以执行操作,并且操作需要一些参数来完成其工作。我为所有操作定义了以下基类(为了您的阅读乐趣而简化):
public abstract class BaseBusinessAction<TActionParameters>
: where TActionParameters : IActionParameters
{
protected BaseBusinessAction(TActionParameters actionParameters)
{
if (actionParameters == null)
throw new ArgumentNullException("actionParameters");
this.Parameters = actionParameters;
if (!ParametersAreValid())
throw new ArgumentException("Valid parameters must be supplied", "actionParameters");
}
protected TActionParameters Parameters { get; private set; }
protected abstract bool ParametersAreValid();
public void CommonMethod() { ... }
}
Only a concrete implementation of BaseBusinessAction
knows how to validate that the parameters passed to it are valid, and therefore the ParametersAreValid
is an abstract function. However, I want the base class constructor to enforce that the parameters passed are always valid, so I've added a call to ParametersAreValid
to the constructor and I throw an exception when the function returns false
. So far so good, right? Well, no. Code analysis is telling me to "not call overridable methods in constructors" which actually makes a lot of sense because when the base class's constructor is called the child class's constructor has not yet been called, and therefore the ParametersAreValid
method may not have access to some critical member variable that the child class's constructor would set.
只有BaseBusinessAction的具体实现知道如何验证传递给它的参数是否有效,因此ParametersAreValid是一个抽象函数。但是,我希望基类构造函数强制传递的参数始终有效,因此我在构造函数中添加了对ParametersAreValid的调用,并在函数返回false时抛出异常。到目前为止一切都那么好吧?好吧,不。代码分析告诉我“不要在构造函数中调用可覆盖的方法”这实际上很有意义,因为当调用基类的构造函数时,尚未调用子类的构造函数,因此ParametersAreValid方法可能无法访问某些子类的构造函数将设置的关键成员变量。
So the question is this: How do I improve this design?
所以问题是:我如何改进这种设计?
Do I add a Func<bool, TActionParameters>
parameter to the base class constructor? If I did:
我是否将Func
public class MyAction<MyParameters>
{
public MyAction(MyParameters actionParameters, bool something) : base(actionParameters, ValidateIt)
{
this.something = something;
}
private bool something;
public static bool ValidateIt()
{
return something;
}
}
This would work because ValidateIt
is static, but I don't know... Is there a better way?
这可行,因为ValidateIt是静态的,但我不知道......有更好的方法吗?
Comments are very welcome.
评论非常欢迎。
7 个解决方案
#1
7
This is a common design challenge in an inheritance hierarchy - how to perform class-dependent behavior in the constructor. The reason code analysis tools flag this as a problem is that the constructor of the derived class has not yet had an opportunity to run at this point, and the call to the virtual method may depend on state that has not been initialized.
这是继承层次结构中的常见设计挑战 - 如何在构造函数中执行依赖于类的行为。代码分析工具将此标记为问题的原因是派生类的构造函数尚未有机会在此时运行,并且对虚方法的调用可能取决于尚未初始化的状态。
So you have a few choices here:
所以你有几个选择:
- Ignore the problem. If you believe that implementers should be able to write a parameter validation method without relying on any runtime state of the class, then document that assumption and stick with your design.
- Move validation logic into each derived class constructor, have the base class perform just the most basic, abstract kinds of validations it must (null checks, etc).
- Duplicate the logic in each derived class. This kind of code duplication seems unsettling, and it opens the door for derived classes to forget to perform the necessary setup or validation logic.
- Provide an Initialize() method of some kind that has to be called by the consumer (or factory for your type) that will ensure that this validation is performed after the type is fully constructed. This may not be desirable, since it requires that anyone who instantiates your class must remember to call the initialization method - which you would think a constructor could perform. Often, a Factory can help avoid this problem - it would be the only one allowed to instantiate your class, and would call the initialization logic before returning the type to the consumer.
- If validation does not depend on state, then factor the validator into a separate type, which you could even make part of the generic class signature. You could then instantiate the validator in the constructor, pass the parameters to it. Each derived class could define a nested class with a default constructor, and place all parameter validation logic there. A code example of this pattern is provided below.
忽略这个问题。如果您认为实现者应该能够编写参数验证方法而不依赖于类的任何运行时状态,那么请记录该假设并坚持使用您的设计。
将验证逻辑移动到每个派生类构造函数中,让基类执行它必须的最基本,抽象的验证(null检查等)。
复制每个派生类中的逻辑。这种代码重复似乎令人不安,它为派生类忘记执行必要的设置或验证逻辑打开了大门。
提供某种类型的Initialize()方法,该方法必须由使用者(或您的类型的工厂)调用,以确保在完全构造类型之后执行此验证。这可能是不可取的,因为它要求任何实例化您的类的人必须记住调用初始化方法 - 您认为构造函数可以执行该方法。通常,工厂可以帮助避免这个问题 - 它将是唯一允许实例化您的类,并在将类型返回给使用者之前调用初始化逻辑。
如果验证不依赖于状态,则将验证器分解为单独的类型,您甚至可以将其作为通用类签名的一部分。然后,您可以在构造函数中实例化验证器,将参数传递给它。每个派生类都可以使用默认构造函数定义嵌套类,并将所有参数验证逻辑放在那里。下面提供了该模式的代码示例。
When possible, have each constructor perform the validation. But this isn't always desirable. In that case, I personally, prefer the factory pattern because it keeps the implementation straight forward, and it also provides an interception point where other behavior can be added later (logging, caching, etc). However, sometimes factories don't make sense, and in that case I would seriously consider the fourth option of creating a stand-along validator type.
如果可能,让每个构造函数执行验证。但这并不总是令人满意的。在这种情况下,我个人更喜欢工厂模式,因为它使实现保持直接,并且它还提供了一个拦截点,可以在以后添加其他行为(日志记录,缓存等)。但是,有时工厂没有意义,在这种情况下,我会认真考虑创建一个独立验证器类型的第四个选项。
Here's the code example:
这是代码示例:
public interface IParamValidator<TParams>
where TParams : IActionParameters
{
bool ValidateParameters( TParams parameters );
}
public abstract class BaseBusinessAction<TActionParameters,TParamValidator>
where TActionParameters : IActionParameters
where TParamValidator : IParamValidator<TActionParameters>, new()
{
protected BaseBusinessAction(TActionParameters actionParameters)
{
if (actionParameters == null)
throw new ArgumentNullException("actionParameters");
// delegate detailed validation to the supplied IParamValidator
var paramValidator = new TParamValidator();
// you may want to implement the throw inside the Validator
// so additional detail can be added...
if( !paramValidator.ValidateParameters( actionParameters ) )
throw new ArgumentException("Valid parameters must be supplied", "actionParameters");
this.Parameters = actionParameters;
}
}
public class MyAction : BaseBusinessAction<MyActionParams,MyActionValidator>
{
// nested validator class
private class MyActionValidator : IParamValidator<MyActionParams>
{
public MyActionValidator() {} // default constructor
// implement appropriate validation logic
public bool ValidateParameters( MyActionParams params ) { return true; /*...*/ }
}
}
#2
5
If you are deferring to the child class to validate the parameters anyway, why not simply do this in the child class constructor? I understand the principle you are striving for, namely, to enforce that any class that derives from your base class validates its parameters. But even then, users of your base class could simply implement a version of ParametersAreValid()
that simply returns true, in which case, the class has abided by the letter of the contract, but not the spirit.
如果您仍然推迟子类来验证参数,为什么不在子类构造函数中简单地执行此操作?我理解您正在努力的原则,即强制执行从您的基类派生的任何类验证其参数。但即使这样,基类的用户也可以简单地实现一个只返回true的ParametersAreValid()版本,在这种情况下,类遵守合同的字母,而不是精神。
For me, I usually put this kind of validation at the beginning of whatever method is being called. For example,
对我来说,我通常会在调用任何方法的开头进行这种验证。例如,
public MyAction(MyParameters actionParameters, bool something)
: base(actionParameters)
{
#region Pre-Conditions
if (actionParameters == null) throw new ArgumentNullException();
// Perform additional validation here...
#endregion Pre-Conditions
this.something = something;
}
I hope this helps.
我希望这有帮助。
#3
3
I would recommend applying the Single Responsibility Principle to the problem. It seems that the Action class should be responsible for one thing; executing the action. Given that, the validation should be moved to a separate object which is responsible only for validation. You could possibly use some generic interface such as this to define the validator:
我建议将单一责任原则应用于问题。似乎Action类应该对一件事负责;执行动作。鉴于此,应将验证移至单独的对象,该对象仅负责验证。您可以使用某些通用接口来定义验证器:
IParameterValidator<TActionParameters>
{
Validate(TActionParameters parameters);
}
You can then add this to your base constructor, and call the validate method there:
然后,您可以将其添加到基础构造函数中,并在那里调用validate方法:
protected BaseBusinessAction(IParameterValidator<TActionParameters> validator, TActionParameters actionParameters)
{
if (actionParameters == null)
throw new ArgumentNullException("actionParameters");
this.Parameters = actionParameters;
if (!validator.Validate(actionParameters))
throw new ArgumentException("Valid parameters must be supplied", "actionParameters");
}
There is a nice hidden benefit to this approach, which is it allows you to more easily re-use validation rules that are common across actions. If your using an IoC container, then you can also easily add binding conventions to automatically bind IParameterValidator implementations appropriately based on the type of TActionParameters
这种方法有一个很好的隐藏好处,它允许您更轻松地重用跨操作通用的验证规则。如果您使用IoC容器,那么您还可以轻松添加绑定约定,以根据TActionParameters的类型自动绑定IParameterValidator实现
#4
1
I had a very similar issue in the past and I ended up moving the logic to validate parameters to the appropriate ActionParameters
class. This approach would work out of the box if your parameter classes are lined up with BusinessAction classes.
我过去遇到了一个非常类似的问题,最后我将逻辑移动到相应的ActionParameters类中验证参数。如果您的参数类与BusinessAction类对齐,则此方法可以立即使用。
If this is not the case, it gets more painful. You have the following options (I would prefer the first one personally):
如果情况并非如此,则会更加痛苦。你有以下选择(我更喜欢第一个个人):
- Wrap all the parameters in
IValidatableParameters
. The implementations will be lined up with business actions and will provide validation - Just suppress this warning
- Move this check to parent classes, but then you end up with code duplication
- Move this check to the method that actually uses the parameters (but then your code fails later)
在IValidatableParameters中包装所有参数。这些实现将与业务操作排成一行,并将提供验证
只是压制这个警告
将此检查移至父类,但最终会导致代码重复
将此检查移动到实际使用参数的方法(但稍后您的代码会失败)
#5
0
Why not do something like this:
为什么不这样做:
public abstract class BaseBusinessAction<TActionParameters>
: where TActionParameters : IActionParameters
{
protected abstract TActionParameters Parameters { get; }
protected abstract bool ParametersAreValid();
public void CommonMethod() { ... }
}
Now the concrete class has to worry about the parameters and ensuring their validity. I would just enforce the CommonMethod
calling the ParametersAreValid
method prior to doing anything else.
现在,具体类必须担心参数并确保其有效性。我会在执行任何其他操作之前强制执行CommonMethod调用ParametersAreValid方法。
#6
0
How about moving the validation to a more common location in the logic. Instead of running the validation in the constructor, run it on the first (and only the first) call to the method. That way, other developers could construct the object, then change or fix the parameters before executing the action.
如何将验证移动到逻辑中更常见的位置。而不是在构造函数中运行验证,而是在对方法的第一次(也是唯一一次)调用上运行它。这样,其他开发人员可以构造对象,然后在执行操作之前更改或修复参数。
You could do this by altering your getter/setter for the Parameters property, so anything that uses the paramters would validate them on the first use.
你可以通过改变Parameters属性的getter / setter来做到这一点,所以使用参数的任何东西都会在第一次使用时验证它们。
#7
0
Where are the parameters anticipated to be used: from within CommonMethod? It is not clear why the parameters must be valid at the time of instantiation instead of at the time of use and thus you might choose to leave it up to the derived class to validate the parameters before use.
预期使用的参数在哪里:来自CommonMethod?目前尚不清楚为什么参数必须在实例化时而不是在使用时有效,因此您可以选择将其保留到派生类以在使用前验证参数。
EDIT - Given what I know the problem seems to be one of special work needed on construction of the class. That, to me, speaks of a Factory class used to build instances of BaseBusinessAction wherein it would call the virtual Validate() on the instance it builds when it builds it.
编辑 - 鉴于我所知道的问题似乎是建设班级所需的特殊工作之一。对我来说,这就是用于构建BaseBusinessAction实例的Factory类,其中它将在构建它时构建的实例上调用虚拟Validate()。
#1
7
This is a common design challenge in an inheritance hierarchy - how to perform class-dependent behavior in the constructor. The reason code analysis tools flag this as a problem is that the constructor of the derived class has not yet had an opportunity to run at this point, and the call to the virtual method may depend on state that has not been initialized.
这是继承层次结构中的常见设计挑战 - 如何在构造函数中执行依赖于类的行为。代码分析工具将此标记为问题的原因是派生类的构造函数尚未有机会在此时运行,并且对虚方法的调用可能取决于尚未初始化的状态。
So you have a few choices here:
所以你有几个选择:
- Ignore the problem. If you believe that implementers should be able to write a parameter validation method without relying on any runtime state of the class, then document that assumption and stick with your design.
- Move validation logic into each derived class constructor, have the base class perform just the most basic, abstract kinds of validations it must (null checks, etc).
- Duplicate the logic in each derived class. This kind of code duplication seems unsettling, and it opens the door for derived classes to forget to perform the necessary setup or validation logic.
- Provide an Initialize() method of some kind that has to be called by the consumer (or factory for your type) that will ensure that this validation is performed after the type is fully constructed. This may not be desirable, since it requires that anyone who instantiates your class must remember to call the initialization method - which you would think a constructor could perform. Often, a Factory can help avoid this problem - it would be the only one allowed to instantiate your class, and would call the initialization logic before returning the type to the consumer.
- If validation does not depend on state, then factor the validator into a separate type, which you could even make part of the generic class signature. You could then instantiate the validator in the constructor, pass the parameters to it. Each derived class could define a nested class with a default constructor, and place all parameter validation logic there. A code example of this pattern is provided below.
忽略这个问题。如果您认为实现者应该能够编写参数验证方法而不依赖于类的任何运行时状态,那么请记录该假设并坚持使用您的设计。
将验证逻辑移动到每个派生类构造函数中,让基类执行它必须的最基本,抽象的验证(null检查等)。
复制每个派生类中的逻辑。这种代码重复似乎令人不安,它为派生类忘记执行必要的设置或验证逻辑打开了大门。
提供某种类型的Initialize()方法,该方法必须由使用者(或您的类型的工厂)调用,以确保在完全构造类型之后执行此验证。这可能是不可取的,因为它要求任何实例化您的类的人必须记住调用初始化方法 - 您认为构造函数可以执行该方法。通常,工厂可以帮助避免这个问题 - 它将是唯一允许实例化您的类,并在将类型返回给使用者之前调用初始化逻辑。
如果验证不依赖于状态,则将验证器分解为单独的类型,您甚至可以将其作为通用类签名的一部分。然后,您可以在构造函数中实例化验证器,将参数传递给它。每个派生类都可以使用默认构造函数定义嵌套类,并将所有参数验证逻辑放在那里。下面提供了该模式的代码示例。
When possible, have each constructor perform the validation. But this isn't always desirable. In that case, I personally, prefer the factory pattern because it keeps the implementation straight forward, and it also provides an interception point where other behavior can be added later (logging, caching, etc). However, sometimes factories don't make sense, and in that case I would seriously consider the fourth option of creating a stand-along validator type.
如果可能,让每个构造函数执行验证。但这并不总是令人满意的。在这种情况下,我个人更喜欢工厂模式,因为它使实现保持直接,并且它还提供了一个拦截点,可以在以后添加其他行为(日志记录,缓存等)。但是,有时工厂没有意义,在这种情况下,我会认真考虑创建一个独立验证器类型的第四个选项。
Here's the code example:
这是代码示例:
public interface IParamValidator<TParams>
where TParams : IActionParameters
{
bool ValidateParameters( TParams parameters );
}
public abstract class BaseBusinessAction<TActionParameters,TParamValidator>
where TActionParameters : IActionParameters
where TParamValidator : IParamValidator<TActionParameters>, new()
{
protected BaseBusinessAction(TActionParameters actionParameters)
{
if (actionParameters == null)
throw new ArgumentNullException("actionParameters");
// delegate detailed validation to the supplied IParamValidator
var paramValidator = new TParamValidator();
// you may want to implement the throw inside the Validator
// so additional detail can be added...
if( !paramValidator.ValidateParameters( actionParameters ) )
throw new ArgumentException("Valid parameters must be supplied", "actionParameters");
this.Parameters = actionParameters;
}
}
public class MyAction : BaseBusinessAction<MyActionParams,MyActionValidator>
{
// nested validator class
private class MyActionValidator : IParamValidator<MyActionParams>
{
public MyActionValidator() {} // default constructor
// implement appropriate validation logic
public bool ValidateParameters( MyActionParams params ) { return true; /*...*/ }
}
}
#2
5
If you are deferring to the child class to validate the parameters anyway, why not simply do this in the child class constructor? I understand the principle you are striving for, namely, to enforce that any class that derives from your base class validates its parameters. But even then, users of your base class could simply implement a version of ParametersAreValid()
that simply returns true, in which case, the class has abided by the letter of the contract, but not the spirit.
如果您仍然推迟子类来验证参数,为什么不在子类构造函数中简单地执行此操作?我理解您正在努力的原则,即强制执行从您的基类派生的任何类验证其参数。但即使这样,基类的用户也可以简单地实现一个只返回true的ParametersAreValid()版本,在这种情况下,类遵守合同的字母,而不是精神。
For me, I usually put this kind of validation at the beginning of whatever method is being called. For example,
对我来说,我通常会在调用任何方法的开头进行这种验证。例如,
public MyAction(MyParameters actionParameters, bool something)
: base(actionParameters)
{
#region Pre-Conditions
if (actionParameters == null) throw new ArgumentNullException();
// Perform additional validation here...
#endregion Pre-Conditions
this.something = something;
}
I hope this helps.
我希望这有帮助。
#3
3
I would recommend applying the Single Responsibility Principle to the problem. It seems that the Action class should be responsible for one thing; executing the action. Given that, the validation should be moved to a separate object which is responsible only for validation. You could possibly use some generic interface such as this to define the validator:
我建议将单一责任原则应用于问题。似乎Action类应该对一件事负责;执行动作。鉴于此,应将验证移至单独的对象,该对象仅负责验证。您可以使用某些通用接口来定义验证器:
IParameterValidator<TActionParameters>
{
Validate(TActionParameters parameters);
}
You can then add this to your base constructor, and call the validate method there:
然后,您可以将其添加到基础构造函数中,并在那里调用validate方法:
protected BaseBusinessAction(IParameterValidator<TActionParameters> validator, TActionParameters actionParameters)
{
if (actionParameters == null)
throw new ArgumentNullException("actionParameters");
this.Parameters = actionParameters;
if (!validator.Validate(actionParameters))
throw new ArgumentException("Valid parameters must be supplied", "actionParameters");
}
There is a nice hidden benefit to this approach, which is it allows you to more easily re-use validation rules that are common across actions. If your using an IoC container, then you can also easily add binding conventions to automatically bind IParameterValidator implementations appropriately based on the type of TActionParameters
这种方法有一个很好的隐藏好处,它允许您更轻松地重用跨操作通用的验证规则。如果您使用IoC容器,那么您还可以轻松添加绑定约定,以根据TActionParameters的类型自动绑定IParameterValidator实现
#4
1
I had a very similar issue in the past and I ended up moving the logic to validate parameters to the appropriate ActionParameters
class. This approach would work out of the box if your parameter classes are lined up with BusinessAction classes.
我过去遇到了一个非常类似的问题,最后我将逻辑移动到相应的ActionParameters类中验证参数。如果您的参数类与BusinessAction类对齐,则此方法可以立即使用。
If this is not the case, it gets more painful. You have the following options (I would prefer the first one personally):
如果情况并非如此,则会更加痛苦。你有以下选择(我更喜欢第一个个人):
- Wrap all the parameters in
IValidatableParameters
. The implementations will be lined up with business actions and will provide validation - Just suppress this warning
- Move this check to parent classes, but then you end up with code duplication
- Move this check to the method that actually uses the parameters (but then your code fails later)
在IValidatableParameters中包装所有参数。这些实现将与业务操作排成一行,并将提供验证
只是压制这个警告
将此检查移至父类,但最终会导致代码重复
将此检查移动到实际使用参数的方法(但稍后您的代码会失败)
#5
0
Why not do something like this:
为什么不这样做:
public abstract class BaseBusinessAction<TActionParameters>
: where TActionParameters : IActionParameters
{
protected abstract TActionParameters Parameters { get; }
protected abstract bool ParametersAreValid();
public void CommonMethod() { ... }
}
Now the concrete class has to worry about the parameters and ensuring their validity. I would just enforce the CommonMethod
calling the ParametersAreValid
method prior to doing anything else.
现在,具体类必须担心参数并确保其有效性。我会在执行任何其他操作之前强制执行CommonMethod调用ParametersAreValid方法。
#6
0
How about moving the validation to a more common location in the logic. Instead of running the validation in the constructor, run it on the first (and only the first) call to the method. That way, other developers could construct the object, then change or fix the parameters before executing the action.
如何将验证移动到逻辑中更常见的位置。而不是在构造函数中运行验证,而是在对方法的第一次(也是唯一一次)调用上运行它。这样,其他开发人员可以构造对象,然后在执行操作之前更改或修复参数。
You could do this by altering your getter/setter for the Parameters property, so anything that uses the paramters would validate them on the first use.
你可以通过改变Parameters属性的getter / setter来做到这一点,所以使用参数的任何东西都会在第一次使用时验证它们。
#7
0
Where are the parameters anticipated to be used: from within CommonMethod? It is not clear why the parameters must be valid at the time of instantiation instead of at the time of use and thus you might choose to leave it up to the derived class to validate the parameters before use.
预期使用的参数在哪里:来自CommonMethod?目前尚不清楚为什么参数必须在实例化时而不是在使用时有效,因此您可以选择将其保留到派生类以在使用前验证参数。
EDIT - Given what I know the problem seems to be one of special work needed on construction of the class. That, to me, speaks of a Factory class used to build instances of BaseBusinessAction wherein it would call the virtual Validate() on the instance it builds when it builds it.
编辑 - 鉴于我所知道的问题似乎是建设班级所需的特殊工作之一。对我来说,这就是用于构建BaseBusinessAction实例的Factory类,其中它将在构建它时构建的实例上调用虚拟Validate()。