抽象类 (abstract class
):
-
不能直接实例化,只能被继承。
-
用来定义一套基础框架和规范,强制子类必须实现某些方法(抽象方法)。
-
可用来封装一些共通的逻辑,减少代码重复。
虚方法 (virtual
):
-
表示这个方法可以被子类重写(override)。
-
默认给了一套实现,你可以用,也可以替换掉。
-
避免了子类必须每次都写重复代码(子类用基类实现就好)
总结:
抽象类 + 虚方法组合使用的好处是:
-
提供一个统一的接口和逻辑框架
-
允许子类在不破坏主结构的情况下实现个性化逻辑(比如加缓存、记录日志)
🔗 组合使用的优势
抽象类 + 虚方法 |
---|
✅ 定义统一规范和基础结构 |
✅ 提供默认逻辑(虚方法) |
✅ 允许子类按需定制(重写虚方法) |
✅ 提高代码复用性、可维护性 |
✅ 非侵入式扩展逻辑(如:记录日志、缓存等) |
🔧 举个实际应用场景(例如仓储):
cs
public abstract class BaseRepository<T>
{
public virtual void Add(T entity)
{
// 默认实现:记录日志 + 保存
Console.WriteLine("添加前记录日志");
Save(entity);
}
protected abstract void Save(T entity); // 强制子类必须实现
}
cs
public class UserRepository : BaseRepository<User>
{
protected override void Save(User entity)
{
// 实现具体的保存逻辑
Console.WriteLine("保存用户到数据库");
}
public override void Add(User entity)
{
// 也可以选择重写 Add,增加缓存逻辑等
base.Add(entity);
Console.WriteLine("添加用户成功");
}
}
✅ 示例代码:调用 UserRepository
cs
public class Program
{
public static void Main(string[] args)
{
var userRepo = new UserRepository();
var newUser = new User { Id = 1, Name = "张三" };
userRepo.Add(newUser);
/* 日志打印结果
添加前记录日志
保存用户到数据库
添加用户成功
*/
}
}
// 假设 User 类如下:
public class User
{
public int Id { get; set; }
public string Name { get; set; }
}
🎯 实战目标
构建一个 基于接口 + 抽象类 + 泛型 的通用仓储:
-
支持常规操作(增删改查)
-
支持扩展方法(如分页、条件查询)
-
易于继承 & 复用
🧩 步骤一:定义接口 IRepository<T>
cs
public interface IRepository<T> where T : class
{
Task<T> GetByIdAsync(int id);
Task<IEnumerable<T>> GetAllAsync();
Task AddAsync(T entity);
void Update(T entity);
void Delete(T entity);
}
🧱 步骤二:实现抽象类 BaseRepository<T>
以 EF Core 为例,注入 DbContext
:
cs
public abstract class BaseRepository<T> : IRepository<T> where T : class
{
protected readonly DbContext _context;
protected readonly DbSet<T> _dbSet;
public BaseRepository(DbContext context)
{
_context = context;
_dbSet = _context.Set<T>();
}
public virtual async Task<T> GetByIdAsync(int id)
{
return await _dbSet.FindAsync(id);
}
public virtual async Task<IEnumerable<T>> GetAllAsync()
{
return await _dbSet.ToListAsync();
}
public virtual async Task AddAsync(T entity)
{
await _dbSet.AddAsync(entity);
}
public virtual void Update(T entity)
{
_dbSet.Update(entity);
}
public virtual void Delete(T entity)
{
_dbSet.Remove(entity);
}
}
🧪 步骤三:创建具体仓储类 UserRepository
cs
public class UserRepository : BaseRepository<User>
{
public UserRepository(MyDbContext context) : base(context)
{
}
// 可扩展自定义方法
public async Task<User?> GetByEmailAsync(string email)
{
return await _dbSet.FirstOrDefaultAsync(u => u.Email == email);
}
}
🧩 步骤四:在服务中使用
cs
public class UserService
{
private readonly UserRepository _userRepo;
public UserService(UserRepository userRepo)
{
_userRepo = userRepo;
}
public async Task RegisterUser(User user)
{
await _userRepo.AddAsync(user);
// 保存到数据库由 UnitOfWork 或 DbContext 控制
}
}
✅ 什么时候用接口 vs 抽象类?
特性 | 接口(interface) | 抽象类(abstract class) |
---|---|---|
目的 | 定义行为规范 | 定义基本结构和部分实现 |
支持多继承 | ✅ 支持 | ❌ 不支持 |
可包含字段 | ❌ 不行 | ✅ 可以 |
可有构造函数 | ❌ 不行 | ✅ 可以 |
成员默认类型 | 抽象(abstract) | 可以是抽象,也可以有默认实现 |
是否可实例化 | ❌ 不行 | ❌ 不行 |
++仅供学习参考,++