从校验功能浅谈重构

d其实不知道用啥标题,因为这篇文章纯粹的“随笔”。因为今天cnblogs上看见两篇关于校验的文章,《Smark.Data实体成员数据验证》和《浅谈Oxite2的数据访问和数据验证》,加上《代码习惯》这篇文章有些感触,所以就着这两个东西写点模式应用,没有任何贬低两个框架的意思,我只关注技术。
讲的其实都是非常简单的东西,主要受《代码习惯》文章的启发,觉得很多时候道理大家都知道,但是没有去严格要求自己,我这里只是强调一下。

1、单一职责
我们好多代码喜欢把功能堆砌在一起,这个我们就需要拆分开,例如《Smark.Data实体成员数据验证》一文中(您可能需要移步跳转到那篇文章,看一下代码),其红色的代码部分包含了校验功能,但是这个函数却是:NewData,看样子是做插入操作的,因此我不建议此函数中包含具体的校验实现。这个时候我们应该重构他,让这些功能移动到某个类,最好是某个服务中,这样让NewData更加单一职责。
在第二篇文章《浅谈Oxite2的数据访问和数据验证》中提及的Oxite2框架中,我看了其代码的实现,其就定义了IValidationService接口。
    public interface IValidationService
    {
        ValidationState Validate
<T>(T model);
    }

一个函数甚至一个类就做一类事情。道理很简单,我就不啰嗦了。

2、简单设计
这回拿Oxite2说事,咱先来看其校验服务的默认实现:

    public class ValidationService : IValidationService
    {
        
private readonly IUnityContainer container;

        
public ValidationService(IUnityContainer container)
        {
            
this.container = container;
        }

        
public IValidator<T> GetValidatorFor<T>(T entity)
        {
            
return container.Resolve<IValidator<T>>();
        }

        
public ValidationState Validate<T>(T entity)
        {
            IValidator
<T> validator = GetValidatorFor(entity);
            
            
if (validator == null// or just return null?
                throw new Exception(string.Format("No validator found for type ({0})", entity.GetType()));

            
return validator.Validate(entity);
        }
    }

很容易理解其代码的用意,注意其GetValidatorFor的实现,我们会发现他从容器中获取一个校验器实例,而且是唯一的实例。这个问题就来了:
为什么我需要IValidator<T>?
IValidationService是负责校验的,IValidator也是负责校验的,两个职责重叠了。现在的代码是先调用校验服务,然后校验服务转发给具体实体类型的校验器。我觉得完全可以删除IValidator<T>,修改为IValidationService<T>。
后话:我注意到IValidationService算是全局服务,非泛型的,而IValidator<T>是泛型的,针对每种类型的实体。作者意图可能希望起到一个转发的作用,但是我认为,既然可以配置IValidator<T>,一样可以配置出IValidationService<T>。
很多程序现在热衷于各种设计模式,讲指令转发过来转发过去,其实很多时候简单才好。

3、简单细节
既然是随笔,就没有那么多的1234了,看到哪里就说到哪里。
下面是Oxite2中错误的代码:

        public ValidationState()
        {
            Errors 
= new ValidationErrorCollection();
        }

        
public ValidationState(IEnumerable<ValidationError> errors)
            : 
this()
        {
            
if (errors != null)
                
foreach (ValidationError error in errors)
                    Errors.Add(error);
        }

这个代码有问题,在构造函数中,应该是参数少的调用参数多的,如果需要调用基类的构造,应该让参数最多的构造调用基类参数最多的构造
一来和方法的调用规则一致,二来不用看代码跳来跳去。下面是正确的代码:
        public ValidationState() : this(null)
        {
        }

        
public ValidationState(IEnumerable<ValidationError> errors)
        {
            
this.Errors = new ValidationErrorCollection();
            
if (errors != null)
            {
                
foreach (ValidationError error in errors)
                    Errors.Add(error);
            }
        }


还有一个地方就是封装集合类的问题,我的观点是,如果你定义了集合类,一般就应该具有封装性,简单的说就是不要派生自List<T>或Dictionary<TK,TV>,不然你封装他干嘛?
下面是Oxite2中错误的代码,他错误的从List上继承。
public class ValidationErrorCollection : List<ValidationError>
{
    
public void Add(string name, object attemptedValue, string message)
    {
        Add(
new ValidationError(name, attemptedValue, message));
    }

    
public void Add(string name, object attemptedValue, Exception exception)
    {
        Add(
new ValidationError(name, attemptedValue, exception));
    }
}
正确的方法是从Collection<T>上派生,需要注意的是Collection<T>是允许插入null的,所以请记得重载插入操作禁止插入null。

最后就是注释了,Oxite2注释好少啊。
原文地址:https://www.cnblogs.com/tansm/p/1567289.html