我该如何改进这个设计?

Kla*_*sen 16 .net c# oop generics

假设我们的系统可以执行操作,并且操作需要一些参数来完成其工作.我为所有操作定义了以下基类(简化了您的阅读乐趣):

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() { ... }
}
Run Code Online (Sandbox Code Playgroud)

只有具体实现BaseBusinessAction知道如何验证传递给它的参数是否有效,因此它 ParametersAreValid是一个抽象函数.但是,我希望基类构造函数强制传递的参数始终有效,所以我添加了ParametersAreValid对构造函数的调用,并在函数返回时抛出异常false.到目前为止一切都那么好吧?好吧,不.代码分析告诉我" 不要在构造函数中调用可覆盖的方法 "这实际上很有意义,因为当调用基类的构造函数时,子类的构造函数尚未被调用,因此该ParametersAreValid方法可能无法访问某些关键子类的构造函数将设置的成员变量.

所以问题是:我如何改进这个设计?

我是否Func<bool, TActionParameters>向基类构造函数添加了一个参数?如果我做了:

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

}
Run Code Online (Sandbox Code Playgroud)

这会起作用,因为它ValidateIt是静态的,但我不知道......有更好的方法吗?

评论非常欢迎.

LBu*_*kin 7

这是继承层次结构中的常见设计挑战 - 如何在构造函数中执行依赖于类的行为.代码分析工具将此标记为问题的原因是派生类的构造函数尚未有机会在此时运行,并且对虚方法的调用可能取决于尚未初始化的状态.

所以你有几个选择:

  1. 忽略这个问题.如果您认为实现者应该能够编写参数验证方法而不依赖于类的任何运行时状态,那么请记录该假设并坚持使用您的设计.
  2. 将验证逻辑移动到每个派生类构造函数中,让基类执行它必须的最基本,抽象的验证类型(空检查等).
  3. 复制每个派生类中的逻辑.这种代码重复似乎令人不安,它为派生类忘记执行必要的设置或验证逻辑打开了大门.
  4. 提供某种类型的Initialize()方法,方法必须由使用者(或您的类型的工厂)调用,以确保在完全构造类型之后执行此验证.这可能是不可取的,因为它要求任何实例化您的类的人必须记住调用初始化方法 - 您认为构造函数可以执行该方法.通常,工厂可以帮助避免这个问题 - 它将是唯一一个允许实例化您的类,并在将类型返回给使用者之前调用初始化逻辑.
  5. 如果验证不依赖于状态,则将验证器分解为单独的类型,您甚至可以将其作为通用类签名的一部分.然后,您可以在构造函数中实例化验证器,将参数传递给它.每个派生类都可以使用默认构造函数定义嵌套类,并将所有参数验证逻辑放在那里.下面提供了该模式的代码示例.

如果可能,让每个构造函数执行验证.但这并不总是令人满意的.在这种情况下,我个人更喜欢工厂模式,因为它使实现保持直接,并且它还提供了一个拦截点,可以在以后添加其他行为(日志记录,缓存等).但是,有时工厂没有意义,在这种情况下,我会认真考虑创建一个独立验证器类型的第四个选项.

这是代码示例:

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; /*...*/ }
    }
}
Run Code Online (Sandbox Code Playgroud)


Mat*_*vis 5

如果您仍然推迟使用子类来验证参数,为什么不在子类构造函数中简单地执行此操作?我理解您正在努力的原则,即强制执行从您的基类派生的任何类验证其参数.但即便如此,基类的用户可以简单地实现一个ParametersAreValid()只返回true 的版本,在这种情况下,该类遵守合同的字母,而不是精神.

对我来说,我通常会在调用任何方法的开头进行这种验证.例如,

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; 
} 
Run Code Online (Sandbox Code Playgroud)

我希望这有帮助.