mi-assessment/docs/开发规范/1-编程规约/1.5-OOP规范.md
2026-02-03 20:50:51 +08:00

21 KiB
Raw Blame History

1.5 OOP规范


1. 类的职责划分

1.1 单一职责原则

// ❌ 不建议:一个类承担多个职责
public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
    // 数据访问职责
    public void SaveToDatabase() { }
    // 邮件发送职责
    public void SendWelcomeEmail() { }
    // 日志记录职责
    public void LogUserAction() { }
    // 数据验证职责
    public bool ValidateEmail() { }
}
// ✅ 建议:职责分离
public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
}
public class UserRepository
{
    public async Task SaveAsync(User user)
    {
        // 数据访问逻辑
    }
}
public class UserEmailService
{
    public async Task SendWelcomeEmailAsync(User user)
    {
        // 邮件发送逻辑
    }
}
public class UserValidator
{
    public bool IsValidEmail(string email)
    {
        // 验证逻辑
    }
}

1.2 类的大小控制

// - 方法数量:建议不超过 20 个
// - 代码行数:建议不超过 300 行
// - 依赖数量:建议不超过 5 个
public class OrderService
{
    // 依赖注入建议最多5个依赖
    private readonly IOrderRepository _orderRepository;
    private readonly IProductService _productService;
    private readonly IEmailService _emailService;
    private readonly ILogger<OrderService> _logger;
    public OrderService(
        IOrderRepository orderRepository,
        IProductService productService,
        IEmailService emailService,
        ILogger<OrderService> logger)
    {
        _orderRepository = orderRepository;
        _productService = productService;
        _emailService = emailService;
        _logger = logger;
    }
    // 核心业务方法
    public async Task<Order> CreateOrderAsync(CreateOrderRequest request)
    {
        // 方法体不超过30行
    }
}
// ❌ 如果类过大,考虑拆分
// 例如:将订单创建、订单查询、订单取消拆分为不同的服务类
public class OrderCreationService { }
public class OrderQueryService { }
public class OrderCancellationService

2. 封装原则

2.1 字段私有化

// ❌ 不建议:公开字段
public class BankAccount
{
    public decimal Balance; // 可被外部直接修改
}
// ✅ 建议:私有字段 + 公共属性
public class BankAccount
{
    private decimal _balance;
    public decimal Balance 
    { 
        get => _balance;
        private set => _balance = value; // 只能内部修改
    }
    // 通过方法控制状态变化
    public void Deposit(decimal amount)
    {
        if (amount <= 0)
        {
            throw new ArgumentException("存款金额必须大于0");
        } 
        _balance += amount;
    }
    public void Withdraw(decimal amount)
    {
        if (amount <= 0)
        {
            throw new ArgumentException("取款金额必须大于0");
        }
        if (amount > _balance)
        {
            throw new InvalidOperationException("余额不足");
        }   
        _balance -= amount;
    }
}

2.2 属性设计

public class Product
{
    // ✅ 自动属性(简单场景)
    public int Id { get; set; }
    public string Name { get; set; }
    // ✅ 只读属性(外部只能读取)
    public DateTime CreateTime { get; private set; }
    // ✅ 计算属性(不存储值)
    public decimal TotalPrice => Price * Quantity;
    // ✅ 带验证的属性
    private decimal _price;
    public decimal Price
    {
        get => _price;
        set
        {
            if (value < 0)
            {
                throw new ArgumentException("价格不能为负数");
            }   
            _price = value;
        }
    }
    // ✅ 延迟初始化属性
    private List<Review> _reviews;
    public List<Review> Reviews => _reviews ??= new List<Review>();
    // ✅ init 访问器(只能在构造时设置)
    public string Code { get; init; }
    // ✅ required 修饰符C# 11+
    public required string Category { get; init; }
}
// 使用示例
var product = new Product
{
    Code = "P001",
    Category = "电子产品", // 必须设置
    Price = 999.99m
};
// product.Code = "P002"; // 编译错误init 属性不可修改

2.3 对象不变性

// ✅ 不可变对象(线程安全)
public class Money
{
    public decimal Amount { get; }
    public string Currency { get; }
    public Money(decimal amount, string currency)
    {
        if (amount < 0)
        {
           throw new ArgumentException("金额不能为负数");
        }
        Amount = amount;
        Currency = currency ?? throw new ArgumentNullException(nameof(currency));
    }
    // 操作返回新对象,而不修改当前对象
    public Money Add(Money other)
    {
        if (Currency != other.Currency)
        {
            throw new InvalidOperationException("货币类型不同");
        }   
        return new Money(Amount + other.Amount, Currency);
    }
    public Money Multiply(decimal factor)
    {
        return new Money(Amount * factor, Currency);
    }
}
// ✅ 使用 record 创建不可变对象
public record Address(string Street, string City, string ZipCode);
// 使用
var address = new Address("中山路100号", "上海", "200000");
// address.Street = "新地址"; // 编译错误
// 创建副本并修改
var newAddress = address with { Street = "南京路200号" };

3. 继承使用原则

3.1 何时使用继承

// ✅ 正确使用继承
public abstract class Animal
{
    public string Name { get; set; }
    public abstract void MakeSound();
    public virtual void Eat()
    {
        Console.WriteLine($"{Name} is eating");
    }
}
public class Dog : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine("Woof!");
    }
}
public class Cat : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine("Meow!");
    }
    public override void Eat()
    {
        base.Eat(); // 调用基类实现
        Console.WriteLine("Cat loves fish");
    }
}
// ❌ 错误使用继承:仅为复用代码
public class Stack : List<int> // 不应该继承 List
{
    // Stack 不是一个 List
}
// ✅ 正确:使用组合
public class Stack
{
    private readonly List<int> _items = new();
    public void Push(int item) => _items.Add(item);
    public int Pop()
    {
        var item = _items[^1];
        _items.RemoveAt(_items.Count - 1);
        return item;
    }
}

3.2 继承层次控制

// ✅ 继承深度建议不超过3层
// Level 1
public abstract class Entity
{
    public int Id { get; set; }
}
// Level 2
public abstract class AuditableEntity : Entity
{
    public DateTime CreateTime { get; set; }
    public DateTime? UpdateTime { get; set; }
}
// Level 3
public class Product : AuditableEntity
{
    public string Name { get; set; }
    public decimal Price { get; set; }
}
// ❌ 避免更深的继承层次
// Level 4 - 过深
public class SpecialProduct : Product { } // 考虑重新设计

3.3 虚方法使用规范

public abstract class PaymentProcessor
{
    // ✅ 模板方法模式
    public async Task<PaymentResult> ProcessPaymentAsync(PaymentRequest request)
    {
        // 1. 验证(子类可重写)
        ValidateRequest(request);
        // 2. 预处理(子类可重写)
        await PreProcessAsync(request);
        // 3. 核心处理(子类必须实现)
        var result = await ExecutePaymentAsync(request);
        // 4. 后处理(子类可重写)
        await PostProcessAsync(result);
        return result;
    }
    // 虚方法(可选重写)
    protected virtual void ValidateRequest(PaymentRequest request)
    {
        if (request.Amount <= 0)
        {
            throw new ArgumentException("支付金额必须大于0");
        }
    }
    protected virtual Task PreProcessAsync(PaymentRequest request)
    {
        return Task.CompletedTask;
    }
    // 抽象方法(必须实现)
    protected abstract Task<PaymentResult> ExecutePaymentAsync(PaymentRequest request);
    protected virtual Task PostProcessAsync(PaymentResult result)
    {
        return Task.CompletedTask;
    }
}
// 具体实现
public class AlipayProcessor : PaymentProcessor
{
    protected override async Task<PaymentResult> ExecutePaymentAsync(PaymentRequest request)
    {
        // 支付宝支付逻辑
        return new PaymentResult { Success = true };
    }
    protected override async Task PostProcessAsync(PaymentResult result)
    {
        // 发送支付通知
        await base.PostProcessAsync(result);
    }
}

4. 组合优于继承

4.1 使用组合的场景

// ❌ 错误:使用继承实现不同功能组合
public class FlyingSwimmingAnimal : Animal { } // 会飞会游泳的动物?
// ✅ 正确:使用组合和接口
public interface IFlyable
{
    void Fly();
}
public interface ISwimmable
{
    void Swim();
}
public class Duck : Animal, IFlyable, ISwimmable
{
    private readonly FlyingAbility _flyingAbility = new();
    private readonly SwimmingAbility _swimmingAbility = new();
    public void Fly() => _flyingAbility.Fly();
    public void Swim() => _swimmingAbility.Swim();
    public override void MakeSound()
    {
        Console.WriteLine("Quack!");
    }
}
public class FlyingAbility
{
    public void Fly()
    {
        Console.WriteLine("Flying in the sky");
    }
}
public class SwimmingAbility
{
    public void Swim()
    {
        Console.WriteLine("Swimming in the water");
    }
}

4.2 策略模式代替继承

// ✅ 使用策略模式提供不同行为
public interface IShippingStrategy
{
    decimal CalculateCost(decimal weight, decimal distance);
}
public class StandardShipping : IShippingStrategy
{
    public decimal CalculateCost(decimal weight, decimal distance)
    {
        return weight * 0.5m + distance * 0.1m;
    }
}
public class ExpressShipping : IShippingStrategy
{
    public decimal CalculateCost(decimal weight, decimal distance)
    {
        return weight * 1.0m + distance * 0.2m + 20m;
    }
}
public class Order
{
    private IShippingStrategy _shippingStrategy;
    public void SetShippingStrategy(IShippingStrategy strategy)
    {
        _shippingStrategy = strategy ?? throw new ArgumentNullException(nameof(strategy));
    }
    public decimal CalculateShippingCost(decimal weight, decimal distance)
    {
        if (_shippingStrategy == null)
        {
            throw new InvalidOperationException("运输策略未设置");
        }   
        return _shippingStrategy.CalculateCost(weight, distance);
    }
}

5. 接口设计原则

5.1 接口命名与定义

// ✅ 接口命名以 I 开头
public interface ICustomerRepository
{
    Task<Customer> GetByIdAsync(int id);
    Task<IEnumerable<Customer>> GetAllAsync();
    Task<Customer> AddAsync(Customer customer);
    Task UpdateAsync(Customer customer);
    Task DeleteAsync(int id);
}
// ✅ 能力接口(形容词)
public interface IDisposable
{
    void Dispose();
}
public interface IComparable<T>
{
    int CompareTo(T other);
}
// ✅ 服务接口(名词)
public interface IEmailService
{
    Task SendAsync(string to, string subject, string body);
}
public interface ILogger<T>
{
    void LogInformation(string message);
    void LogError(Exception ex, string message);
}

5.2 接口隔离

// ❌ 不建议:臃肿的接口
public interface IRepository
{
    // CRUD 操作
    void Add();
    void Update();
    void Delete();
    void Get();
    // 批量操作
    void BulkAdd();
    void BulkUpdate();
    void BulkDelete();
    // 搜索功能
    void Search();
    void AdvancedSearch();
    // 导出功能
    void ExportToExcel();
    void ExportToPdf();
}
// ✅ 建议:接口隔离
public interface IReadRepository<T>
{
    Task<T> GetByIdAsync(int id);
    Task<IEnumerable<T>> GetAllAsync();
}
public interface IWriteRepository<T>
{
    Task<T> AddAsync(T entity);
    Task UpdateAsync(T entity);
    Task DeleteAsync(int id);
}
public interface IBulkRepository<T>
{
    Task BulkAddAsync(IEnumerable<T> entities);
    Task BulkUpdateAsync(IEnumerable<T> entities);
}
public interface ISearchableRepository<T>
{
    Task<IEnumerable<T>> SearchAsync(SearchCriteria criteria);
}
// 组合使用
public interface ICustomerRepository : IReadRepository<Customer>, 
                                       IWriteRepository<Customer>,
                                       ISearchableRepository<Customer>
{
    // 特定于 Customer 的方法
    Task<Customer> GetByEmailAsync(string email);
}

5.3 显式接口实现

public interface IAnimal
{
    void Move();
}
public interface IRobot
{
    void Move();
}
// ✅ 显式接口实现解决方法冲突
public class RobotDog : IAnimal, IRobot
{
    // 隐式实现(默认)
    public void Move()
    {
        Console.WriteLine("RobotDog moving");
    }
    // 显式实现 IAnimal
    void IAnimal.Move()
    {
        Console.WriteLine("Animal walking");
    }
    // 显式实现 IRobot
    void IRobot.Move()
    {
        Console.WriteLine("Robot moving mechanically");
    }
}
// 使用
var robotDog = new RobotDog();
robotDog.Move();                    // "RobotDog moving"
((IAnimal)robotDog).Move();         // "Animal walking"
((IRobot)robotDog).Move();          // "Robot moving mechanically"

6. 抽象类使用场景

6.1 抽象类 vs 接口

// ✅ 使用抽象类:有共同实现逻辑
public abstract class Document
{
    // 公共属性
    public string Title { get; set; }
    public DateTime CreateTime { get; set; }
    // 公共方法实现
    public void Save()
    {
        ValidateBeforeSave();
        PerformSave();
        LogSave();
    }
    // 模板方法
    protected virtual void ValidateBeforeSave()
    {
        if (string.IsNullOrWhiteSpace(Title))
        {
            throw new ValidationException("标题不能为空");
        }   
    }
    // 抽象方法(强制子类实现)
    protected abstract void PerformSave();
    // 虚方法(可选重写)
    protected virtual void LogSave()
    {
        Console.WriteLine($"Document '{Title}' saved at {DateTime.Now}");
    }
}
public class PdfDocument : Document
{
    protected override void PerformSave()
    {
        // PDF 特定的保存逻辑
        Console.WriteLine("Saving as PDF");
    }
}
public class WordDocument : Document
{
    protected override void PerformSave()
    {
        // Word 特定的保存逻辑
        Console.WriteLine("Saving as Word document");
    }
    protected override void ValidateBeforeSave()
    {
        base.ValidateBeforeSave();
        // 额外的验证逻辑
    }
}
// ✅ 使用接口:定义契约,无共同实现
public interface INotificationSender
{
    Task SendAsync(string recipient, string message);
}
public class EmailSender : INotificationSender
{
    public async Task SendAsync(string recipient, string message)
    {
        // 邮件发送实现
    }
}
public class SmsSender : INotificationSender
{
    public async Task SendAsync(string recipient, string message)
    {
        // 短信发送实现
    }
}

6.2 抽象类设计要点

public abstract class BaseEntity
{
    // ✅ 抽象类可以有构造函数
    protected BaseEntity()
    {
        CreateTime = DateTime.UtcNow;
        Id = Guid.NewGuid();
    }
    // ✅ 可以有字段
    private readonly List<DomainEvent> _domainEvents = new();
    // 公共属性
    public Guid Id { get; private set; }
    public DateTime CreateTime { get; private set; }
    public DateTime? UpdateTime { get; protected set; }
    // ✅ 受保护的方法供子类使用
    protected void AddDomainEvent(DomainEvent domainEvent)
    {
        _domainEvents.Add(domainEvent);
    }
    protected void MarkAsUpdated()
    {
        UpdateTime = DateTime.UtcNow;
    }
    // 抽象方法
    public abstract void Validate();
}
public class Customer : BaseEntity
{
    public string Name { get; set; }
    public string Email { get; set; }
    public override void Validate()
    {
        if (string.IsNullOrWhiteSpace(Name))
        {
            throw new ValidationException("客户名称不能为空");
        }
        if (string.IsNullOrWhiteSpace(Email))
        {
            throw new ValidationException("邮箱不能为空");
        }
    }
    public void UpdateEmail(string newEmail)
    {
        Email = newEmail;
        MarkAsUpdated(); // 使用基类方法
        AddDomainEvent(new CustomerEmailChangedEvent(Id, newEmail));
    }
}

7. 密封类使用

7.1 何时使用 sealed

// ✅ 防止被继承(性能优化、设计意图)
public sealed class ConfigurationManager
{
    private readonly Dictionary<string, string> _settings = new();
    public string GetSetting(string key)
    {
        return _settings.TryGetValue(key, out var value) ? value : null;
    }
}
// ❌ 无法继承
// public class CustomConfigManager : ConfigurationManager { } // 编译错误
// ✅ 工具类密封
public sealed class StringHelper
{
    private StringHelper() { } // 私有构造防止实例化
    public static string Truncate(string value, int maxLength)
    {
        if (string.IsNullOrEmpty(value) || value.Length <= maxLength)
        {
            return value;
        }   
        return value.Substring(0, maxLength) + "...";
    }
}
// ✅ 密封重写方法(防止进一步重写)
public class BaseProcessor
{
    public virtual void Process()
    {
        Console.WriteLine("Base processing");
    }
}
public class DerivedProcessor : BaseProcessor
{
    // 密封此方法,子类不能再重写
    public sealed override void Process()
    {
        Console.WriteLine("Derived processing");
        base.Process();
    }
}
public class FurtherDerived : DerivedProcessor
{
    // ❌ 编译错误:不能重写密封方法
    // public override void Process() { }
}    

8. 访问修饰符使用规范

8.1 访问级别选择

public class Product
{
    // ✅ private - 只在类内部使用
    private decimal _costPrice;
    private List<PriceHistory> _priceHistory = new();
    // ✅ private protected - 仅当前类和派生类访问(同一程序集)
    private protected void RecordPriceChange(decimal oldPrice, decimal newPrice)
    {
        _priceHistory.Add(new PriceHistory(oldPrice, newPrice));
    }
    // ✅ protected - 当前类和派生类可访问
    protected decimal CostPrice => _costPrice;
    // ✅ internal - 同一程序集内可访问
    internal void SetCostPrice(decimal cost)
    {
        _costPrice = cost;
    }
    // ✅ protected internal - 同一程序集或派生类
    protected internal decimal CalculateMargin()
    {
        return Price - _costPrice;
    }
    // ✅ public - 所有地方可访问
    public decimal Price { get; set; }
    public string Name { get; set; }
    public decimal GetProfitMargin()
    {
        return (Price - _costPrice) / Price * 100;
    }
}
// 访问修饰符决策树:
// 1. 只在当前类使用?           -> private
// 2. 需要子类访问?             -> protected
// 3. 同一程序集内使用?         -> internal
// 4. 需要外部调用?             -> public
// 5. 同程序集或子类?           -> protected internal
// 6. 仅同程序集的子类?         -> private protected

8.2 最小权限原则

// ✅ 建议:默认使用最小访问权限
public class OrderProcessor
{
    // 私有字段
    private readonly IOrderRepository _repository;
    private readonly ILogger _logger;
    
    // 公共构造函数
    public OrderProcessor(IOrderRepository repository, ILogger logger)
    {
        _repository = repository;
        _logger = logger;
    }
    
    // 公共方法对外API
    public async Task<Order> ProcessOrderAsync(int orderId)
    {
        var order = await GetOrderAsync(orderId);
        ValidateOrder(order);
        await SaveOrderAsync(order);
        return order;
    }
    
    // 私有方法(内部实现细节)
    private async Task<Order> GetOrderAsync(int orderId)
    {
        return await _repository.GetByIdAsync(orderId);
    }
    
    private void ValidateOrder(Order order)
    {
        if (order == null)
        {
            throw new ArgumentNullException(nameof(order));
        }
    }
    
    private async Task SaveOrderAsync(Order order)
    {
        await _repository.UpdateAsync(order);
        _logger.LogInformation("Order {OrderId} saved", order.Id);
    }
}

// ❌ 不建议:不必要的公开
public class BadOrderProcessor
{
    // 不应该公开
    public IOrderRepository Repository { get; set; }
    
    // 不应该公开内部实现
    public void ValidateOrder(Order order) { }
    public async Task SaveOrderAsync(Order order) { }
}