代码之家  ›  专栏  ›  技术社区  ›  Klaus Byskov Pedersen

如何改进这个设计?

  •  16
  • Klaus Byskov Pedersen  · 技术社区  · 16 年前

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

    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() { ... }
    }
    

    BaseBusinessAction 知道如何验证传递给它的参数是否有效,因此 ParametersAreValid 呼叫 参数REVALID 当函数返回时,我抛出一个异常 false not call overridable methods in constructors 参数REVALID 将设置子类的构造函数。

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

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

    这会有用因为 ValidateIt 是静态的,但我不知道。。。有更好的办法吗?

    7 回复  |  直到 16 年前
        1
  •  7
  •   LBushkin    16 年前

    1. 忽略问题。
    2. 将验证逻辑移到每个派生类构造函数中, 让基类只执行它必须执行的最基本、最抽象的验证(空检查等)。
    3. 在每个派生类中复制逻辑。 忘记 执行必要的设置或验证逻辑。
    4. 必须由使用者(或类型的工厂)调用,以确保在完全构造类型之后执行此验证。这可能并不理想,因为它要求实例化类的任何人都必须记住调用初始化方法—您可能认为构造函数可以执行该方法。通常,工厂可以帮助避免这个问题—它是唯一允许实例化类的工厂,并且在将类型返回给使用者之前调用初始化逻辑。
    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; /*...*/ }
        }
    }
    
        2
  •  5
  •   Matt Davis    16 年前

    如果您仍然要使用子类来验证参数,为什么不直接在子类构造函数中进行验证呢?我理解您所追求的原则,即强制任何从基类派生的类验证其参数。但即使这样,基类的用户也可以简单地实现 ParametersAreValid()

    对我来说,我通常把这种验证放在调用任何方法的开头。例如,

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

        3
  •  3
  •   ckramer    16 年前

    我建议使用 Single Responsibility Principle

    IParameterValidator<TActionParameters>
    {
        Validate(TActionParameters parameters);
    }
    

    然后可以将其添加到基本构造函数中,并在那里调用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");
    }
    

    这种方法有一个隐藏的好处,那就是它允许您更容易地重用跨操作通用的验证规则。如果您使用的是IoC容器,那么您还可以轻松地添加绑定约定,以便根据TActionParameters的类型适当地自动绑定IParameterValidator实现

        4
  •  1
  •   Grzenio    16 年前

    我在过去遇到过一个非常类似的问题,最后我将验证参数的逻辑移到了适当的位置 ActionParameters 班级。如果您的参数类与BusinessAction类排列在一起,那么这种方法将是现成的。

    如果不是这样,那就更痛苦了。你有以下选择(我个人更喜欢第一个):

    1. 将所有参数包装在 IValidatableParameters
    2. 只是抑制这个警告
    3. 将此检查移到父类,但最终会导致代码重复
    4. 将此检查移到实际使用参数的方法(但稍后代码会失败)
        5
  •  0
  •   Ken Henderson    16 年前

    public abstract class BaseBusinessAction<TActionParameters> 
        : where TActionParameters : IActionParameters
    {
    
        protected abstract TActionParameters Parameters { get; }
    
        protected abstract bool ParametersAreValid();
    
        public void CommonMethod() { ... }
    }
    

    现在,具体类必须考虑参数并确保其有效性。我会强制执行 CommonMethod 呼叫 ParametersAreValid 方法,然后再做其他事情。

        6
  •  0
  •   John Fisher    16 年前

    您可以通过更改参数属性的getter/setter来实现这一点,这样任何使用参数的东西都会在第一次使用时验证它们。

        7
  •  0
  •   Thomas    16 年前

    预期使用的参数在哪里:来自CommonMethod?不清楚为什么参数必须在实例化时有效,而不是在使用时有效,因此您可以选择让派生类在使用前验证参数。