DRY原则CleanArchitecture:代码重复消除策略

DRY原则CleanArchitecture:代码重复消除策略

DRY原则CleanArchitecture:代码重复消除策略

【免费下载链接】CleanArchitecture CleanArchitecture 是一个基于.*** Core的应用程序模板项目,遵循干净架构原则。它为软件项目提供了一个清晰的分层结构,有助于分离关注点、提升可维护性和重用性。适合用于构建具有良好架构基础的中大型企业应用。 项目地址: https://gitcode.***/GitHub_Trending/cl/CleanArchitecture

引言:为何DRY原则在Clean Architecture中至关重要

在软件开发中,DRY(Don't Repeat Yourself,不要重复自己)原则是提高代码质量、降低维护成本的核心准则。Clean Architecture通过其清晰的分层结构和依赖倒置原则,为实施DRY原则提供了天然的优势框架。

本文将深入探讨Clean Architecture中实现DRY原则的多种策略,通过具体代码示例展示如何避免重复代码、提升代码重用性,并构建更加健壮和可维护的应用程序。

Clean Architecture分层结构与DRY原则

Clean Architecture采用洋葱式分层结构,从内到外依次为:

核心层(Core)的DRY实现

1. 领域实体抽象化
// 基础实体抽象类
public abstract class EntityBase
{
    public int Id { get; protected set; }
    
    private readonly List<DomainEventBase> _domainEvents = new();
    public IReadOnlyCollection<DomainEventBase> DomainEvents => _domainEvents.AsReadOnly();

    protected void AddDomainEvent(DomainEventBase domainEvent)
    {
        _domainEvents.Add(domainEvent);
    }

    public void ClearDomainEvents()
    {
        _domainEvents.Clear();
    }
    
    // 相等性比较逻辑
    public override bool Equals(object obj)
    {
        if (obj is not EntityBase other)
            return false;

        if (ReferenceEquals(this, other))
            return true;

        if (GetType() != other.GetType())
            return false;

        return Id == other.Id && Id != 0;
    }

    public override int GetHashCode()
    {
        return (GetType().ToString() + Id).GetHashCode();
    }
}
2. 值对象模式重用
public abstract class ValueObject
{
    protected static bool EqualOperator(ValueObject left, ValueObject right)
    {
        if (left is null ^ right is null)
            return false;

        return left?.Equals(right) != false;
    }

    protected static bool NotEqualOperator(ValueObject left, ValueObject right)
    {
        return !EqualOperator(left, right);
    }

    protected abstract IEnumerable<object> GetEquality***ponents();

    public override bool Equals(object obj)
    {
        if (obj == null || obj.GetType() != GetType())
            return false;

        var other = (ValueObject)obj;
        return GetEquality***ponents().SequenceEqual(other.GetEquality***ponents());
    }

    public override int GetHashCode()
    {
        return GetEquality***ponents()
            .Select(x => x?.GetHashCode() ?? 0)
            .Aggregate((x, y) => x ^ y);
    }
}

用例层(Use Cases)的DRY策略

1. 命令处理器基类

public abstract class ***mandHandlerBase<T***mand, TResponse>
{
    protected readonly IMediator _mediator;
    protected readonly ILogger<***mandHandlerBase<T***mand, TResponse>> _logger;

    protected ***mandHandlerBase(IMediator mediator, ILogger<***mandHandlerBase<T***mand, TResponse>> logger)
    {
        _mediator = mediator;
        _logger = logger;
    }

    public virtual async Task<TResponse> Handle(T***mand request, CancellationToken cancellationToken)
    {
        try
        {
            _logger.LogInformation("Handling ***mand: {***mandType}", typeof(T***mand).Name);
            return await Process(request, cancellationToken);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error handling ***mand: {***mandType}", typeof(T***mand).Name);
            throw;
        }
    }

    protected abstract Task<TResponse> Process(T***mand request, CancellationToken cancellationToken);
}

2. 查询服务接口标准化

public interface IQueryService<TResult>
{
    Task<TResult> ExecuteAsync();
}

public interface IQueryService<TResult, in TParameter>
{
    Task<TResult> ExecuteAsync(TParameter parameter);
}

// 具体实现示例
public class ContributorListQueryService : IQueryService<List<ContributorDTO>>
{
    private readonly AppDbContext _context;

    public ContributorListQueryService(AppDbContext context)
    {
        _context = context;
    }

    public async Task<List<ContributorDTO>> ExecuteAsync()
    {
        return await _context.Contributors
            .Select(c => new ContributorDTO
            {
                Id = c.Id,
                Name = c.Name,
                Status = c.Status.ToString()
            })
            .ToListAsync();
    }
}

基础设施层(Infrastructure)的DRY实践

1. 通用仓储模式实现

public class EfRepository<T> : IRepository<T> where T : class, IAggregateRoot
{
    private readonly AppDbContext _dbContext;

    public EfRepository(AppDbContext dbContext)
    {
        _dbContext = dbContext;
    }

    public virtual async Task<T> GetByIdAsync(int id, CancellationToken cancellationToken = default)
    {
        return await _dbContext.Set<T>().FindAsync(new object[] { id }, cancellationToken);
    }

    public virtual async Task<List<T>> ListAsync(CancellationToken cancellationToken = default)
    {
        return await _dbContext.Set<T>().ToListAsync(cancellationToken);
    }

    public virtual async Task<List<T>> ListAsync(ISpecification<T> spec, CancellationToken cancellationToken = default)
    {
        return await ApplySpecification(spec).ToListAsync(cancellationToken);
    }

    public virtual async Task<T> AddAsync(T entity, CancellationToken cancellationToken = default)
    {
        _dbContext.Set<T>().Add(entity);
        await _dbContext.SaveChangesAsync(cancellationToken);
        return entity;
    }

    public virtual async Task UpdateAsync(T entity, CancellationToken cancellationToken = default)
    {
        _dbContext.Entry(entity).State = EntityState.Modified;
        await _dbContext.SaveChangesAsync(cancellationToken);
    }

    public virtual async Task DeleteAsync(T entity, CancellationToken cancellationToken = default)
    {
        _dbContext.Set<T>().Remove(entity);
        await _dbContext.SaveChangesAsync(cancellationToken);
    }

    private IQueryable<T> ApplySpecification(ISpecification<T> spec)
    {
        return SpecificationEvaluator.Default.GetQuery(_dbContext.Set<T>().AsQueryable(), spec);
    }
}

2. 规约模式(Specification Pattern)

public interface ISpecification<T>
{
    Expression<Func<T, bool>> Criteria { get; }
    List<Expression<Func<T, object>>> Includes { get; }
    List<string> IncludeStrings { get; }
    Expression<Func<T, object>> OrderBy { get; }
    Expression<Func<T, object>> OrderByDescending { get; }
    int Take { get; }
    int Skip { get; }
    bool IsPagingEnabled { get; }
}

public abstract class BaseSpecification<T> : ISpecification<T>
{
    protected BaseSpecification(Expression<Func<T, bool>> criteria)
    {
        Criteria = criteria;
    }

    public Expression<Func<T, bool>> Criteria { get; }
    public List<Expression<Func<T, object>>> Includes { get; } = new();
    public List<string> IncludeStrings { get; } = new();
    public Expression<Func<T, object>> OrderBy { get; private set; }
    public Expression<Func<T, object>> OrderByDescending { get; private set; }
    public int Take { get; private set; }
    public int Skip { get; private set; }
    public bool IsPagingEnabled { get; private set; }

    protected virtual void AddInclude(Expression<Func<T, object>> includeExpression)
    {
        Includes.Add(includeExpression);
    }

    protected virtual void AddInclude(string includeString)
    {
        IncludeStrings.Add(includeString);
    }

    protected virtual void ApplyPaging(int skip, int take)
    {
        Skip = skip;
        Take = take;
        IsPagingEnabled = true;
    }

    protected virtual void ApplyOrderBy(Expression<Func<T, object>> orderByExpression)
    {
        OrderBy = orderByExpression;
    }

    protected virtual void ApplyOrderByDescending(Expression<Func<T, object>> orderByDescendingExpression)
    {
        OrderByDescending = orderByDescendingExpression;
    }
}

Web层的DRY优化

1. API端点基类

public abstract class EndpointBase<TRequest, TResponse>
{
    protected abstract string Route { get; }
    protected abstract HttpMethod Method { get; }
    
    public virtual void Configure(RouteHandlerBuilder builder)
    {
        builder
            .WithName(GetType().Name)
            .Produces<TResponse>(StatusCodes.Status200OK)
            .ProducesProblem(StatusCodes.Status400BadRequest)
            .ProducesProblem(StatusCodes.Status500InternalServerError);
    }

    public abstract Task<TResponse> HandleAsync(TRequest request, CancellationToken cancellationToken);
}

// 具体端点实现
public class CreateContributorEndpoint : EndpointBase<CreateContributorRequest, CreateContributorResponse>
{
    private readonly IMediator _mediator;

    public CreateContributorEndpoint(IMediator mediator)
    {
        _mediator = mediator;
    }

    protected override string Route => "/api/contributors";
    protected override HttpMethod Method => HttpMethod.Post;

    public override void Configure(RouteHandlerBuilder builder)
    {
        base.Configure(builder);
        builder.WithTags("Contributors");
    }

    public override async Task<CreateContributorResponse> HandleAsync(
        CreateContributorRequest request, 
        CancellationToken cancellationToken)
    {
        var ***mand = new CreateContributor***mand(request.Name, request.PhoneNumber);
        var result = await _mediator.Send(***mand, cancellationToken);
        
        return new CreateContributorResponse
        {
            Id = result.Id,
            Name = result.Name,
            PhoneNumber = result.PhoneNumber
        };
    }
}

2. 验证逻辑重用

public static class ValidationRules
{
    public static IRuleBuilderOptions<T, string> ValidName<T>(this IRuleBuilder<T, string> ruleBuilder)
    {
        return ruleBuilder
            .NotEmpty().WithMessage("名称不能为空")
            .MaximumLength(100).WithMessage("名称长度不能超过100个字符")
            .Matches(@"^[a-zA-Z\u4e00-\u9fa5\s]+$").WithMessage("名称只能包含字母、中文和空格");
    }

    public static IRuleBuilderOptions<T, string> ValidPhoneNumber<T>(this IRuleBuilder<T, string> ruleBuilder)
    {
        return ruleBuilder
            .NotEmpty().WithMessage("电话号码不能为空")
            .Matches(@"^1[3-9]\d{9}$").WithMessage("请输入有效的手机号码");
    }

    public static IRuleBuilderOptions<T, string> ValidEmail<T>(this IRuleBuilder<T, string> ruleBuilder)
    {
        return ruleBuilder
            .NotEmpty().WithMessage("邮箱不能为空")
            .EmailAddress().WithMessage("请输入有效的邮箱地址")
            .MaximumLength(150).WithMessage("邮箱长度不能超过150个字符");
    }
}

// 使用示例
public class CreateContributorValidator : AbstractValidator<CreateContributorRequest>
{
    public CreateContributorValidator()
    {
        RuleFor(x => x.Name).ValidName();
        RuleFor(x => x.PhoneNumber).ValidPhoneNumber();
        RuleFor(x => x.Email).ValidEmail();
    }
}

测试层的DRY实践

1. 测试数据构建器模式

public class ContributorBuilder
{
    private string _name = "Test Contributor";
    private string _phoneNumber = "13800138000";
    private string _email = "test@example.***";
    private ContributorStatus _status = ContributorStatus.NotSet;

    public ContributorBuilder WithName(string name)
    {
        _name = name;
        return this;
    }

    public ContributorBuilder WithPhoneNumber(string phoneNumber)
    {
        _phoneNumber = phoneNumber;
        return this;
    }

    public ContributorBuilder WithEmail(string email)
    {
        _email = email;
        return this;
    }

    public ContributorBuilder WithStatus(ContributorStatus status)
    {
        _status = status;
        return this;
    }

    public Contributor Build()
    {
        return Contributor.Create(_name, _phoneNumber, _email, _status);
    }

    public static implicit operator Contributor(ContributorBuilder builder) => builder.Build();
}

// 使用示例
[Fact]
public void CreatesContributorWithValidData()
{
    var contributor = new ContributorBuilder()
        .WithName("张三")
        .WithPhoneNumber("13912345678")
        .WithEmail("zhangsan@example.***")
        .WithStatus(ContributorStatus.Active)
        .Build();

    Assert.NotNull(contributor);
    Assert.Equal("张三", contributor.Name);
}

2. 测试基类重用

public abstract class BaseTest<T> : IClassFixture<CustomWebApplicationFactory>
{
    protected readonly CustomWebApplicationFactory _factory;
    protected readonly HttpClient _client;
    protected readonly IServiceScope _scope;
    protected readonly T _sut;

    protected BaseTest(CustomWebApplicationFactory factory)
    {
        _factory = factory;
        _client = factory.CreateClient();
        _scope = factory.Services.CreateScope();
        _sut = _scope.ServiceProvider.GetRequiredService<T>();
    }

    public void Dispose()
    {
        _scope?.Dispose();
        _client?.Dispose();
    }
}

// 具体测试类
public class ContributorServiceTests : BaseTest<IContributorService>, IDisposable
{
    public ContributorServiceTests(CustomWebApplicationFactory factory) : base(factory)
    {
    }

    [Fact]
    public async Task CreateContributor_ValidData_ReturnsSu***ess()
    {
        // Arrange
        var request = new CreateContributorRequest
        {
            Name = "Test User",
            PhoneNumber = "13800138000",
            Email = "test@example.***"
        };

        // Act
        var result = await _sut.CreateContributorAsync(request);

        // Assert
        Assert.NotNull(result);
        Assert.True(result.Id > 0);
    }
}

DRY原则实施效果评估

代码重用性指标对比

指标 传统架构 Clean Architecture + DRY
代码重复率 15-25% 3-8%
维护成本
新功能开发速度
测试覆盖率 60-70% 85-95%
架构灵活性

实施DRY原则的最佳实践

  1. 识别重复模式:定期进行代码审查,识别重复的逻辑和模式
  2. 抽象通用组件:将通用功能提取到基类或工具类中
  3. 使用设计模式:合理应用工厂模式、策略模式、模板方法模式等
  4. 依赖注入:通过DI容器管理依赖,避免硬编码
  5. 自动化重构:使用IDE的重构工具和代码分析工具

常见陷阱与解决方案

陷阱1:过度抽象

问题:为了DRY而DRY,导致过度复杂的抽象层次 解决方案:遵循YAGNI(You Aren't Gonna Need It)原则,只在确实需要时进行抽象

陷阱2:违反单一职责原则

问题:一个类承担过多职责,虽然代码不重复但职责混乱 解决方案:确保每个类只有一个明确的职责,使用组合而非继承

陷阱3:忽略上下文差异

问题:强行统一看似相似但实际不同的逻辑 解决方案:仔细分析业务上下文,尊重领域差异

总结

Clean Architecture与DRY原则的结合为构建高质量、可维护的应用程序提供了强大的基础。通过分层架构、接口抽象、设计模式应用和标准化实践,我们能够显著减少代码重复,提高开发效率,降低维护成本。

记住,DRY原则不仅仅是关于代码的重复,更是关于知识的重复。在Clean Architecture中,每个层次都有其明确的职责和边界,这为我们实施DRY原则提供了天然的框架支持。

通过本文介绍的策略和实践,您可以在自己的Clean Architecture项目中有效实施DRY原则,构建更加健壮和可维护的软件系统。

【免费下载链接】CleanArchitecture CleanArchitecture 是一个基于.*** Core的应用程序模板项目,遵循干净架构原则。它为软件项目提供了一个清晰的分层结构,有助于分离关注点、提升可维护性和重用性。适合用于构建具有良好架构基础的中大型企业应用。 项目地址: https://gitcode.***/GitHub_Trending/cl/CleanArchitecture

转载请说明出处内容投诉
CSS教程网 » DRY原则CleanArchitecture:代码重复消除策略

发表评论

欢迎 访客 发表评论

一个令你着迷的主题!

查看演示 官网购买