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

799 lines
21 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 1.5 OOP规范
---
## 1. 类的职责划分
### 1.1 单一职责原则
```csharp
// ❌ 不建议:一个类承担多个职责
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 类的大小控制
```csharp
// - 方法数量:建议不超过 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 字段私有化
```csharp
// ❌ 不建议:公开字段
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 属性设计
```csharp
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 对象不变性
```csharp
// ✅ 不可变对象(线程安全)
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 何时使用继承
```csharp
// ✅ 正确使用继承
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 继承层次控制
```csharp
// ✅ 继承深度建议不超过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 虚方法使用规范
```csharp
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 使用组合的场景
```csharp
// ❌ 错误:使用继承实现不同功能组合
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 策略模式代替继承
```csharp
// ✅ 使用策略模式提供不同行为
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 接口命名与定义
```csharp
// ✅ 接口命名以 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 接口隔离
```csharp
// ❌ 不建议:臃肿的接口
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 显式接口实现
```csharp
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 接口
```csharp
// ✅ 使用抽象类:有共同实现逻辑
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 抽象类设计要点
```csharp
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`
```csharp
// ✅ 防止被继承(性能优化、设计意图)
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 访问级别选择
```csharp
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 最小权限原则
```csharp
// ✅ 建议:默认使用最小访问权限
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) { }
}
```