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原则的最佳实践
- 识别重复模式:定期进行代码审查,识别重复的逻辑和模式
- 抽象通用组件:将通用功能提取到基类或工具类中
- 使用设计模式:合理应用工厂模式、策略模式、模板方法模式等
- 依赖注入:通过DI容器管理依赖,避免硬编码
- 自动化重构:使用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