
在很多项目里,我们经常会遇到这种需求:
如果你也有类似痛点,那你一定得了解一下:插件化开发。
一句话解释:
★插件化就是把“可变的部分”变成独立的模块,主程序只管“框架”,不管“细节”。
更通俗点说:
我们来设计一个简单的系统,要求:
所有插件都必须实现这个接口:
public interface IUserDataImporter
{
string Name { get; }
Task<List<User>> ImportAsync();
}
非常简单:
Name:用来标识插件,比如 "Excel导入"、"CSV导入";ImportAsync:实际导入数据的逻辑,返回一组 User。public classExcelUserImporter : IUserDataImporter
{
publicstring Name => "Excel导入";
publicasync Task<List<User>> ImportAsync()
{
Console.WriteLine("从 Excel 中导入用户...");
await Task.Delay(500); // 模拟读取
returnnew List<User> { new User("Excel用户1"), new User("Excel用户2") };
}
}
public classCsvUserImporter : IUserDataImporter
{
publicstring Name => "CSV导入";
publicasync Task<List<User>> ImportAsync()
{
Console.WriteLine("从 CSV 中导入用户...");
await Task.Delay(500);
returnnew List<User> { new User("CSV用户1") };
}
}
public classApiUserImporter : IUserDataImporter
{
publicstring Name => "API导入";
publicasync Task<List<User>> ImportAsync()
{
Console.WriteLine("从外部 API 导入用户...");
await Task.Delay(500);
returnnew List<User> { new User("API用户A"), new User("API用户B") };
}
}
appsettings.json:{
"EnabledImporters": [ "ExcelUserImporter", "ApiUserImporter" ]
}
var config = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();
var enabled = config.GetSection("EnabledImporters").Get<string[]>();
var importerTypes = Assembly.GetExecutingAssembly()
.GetTypes()
.Where(t => typeof(IUserDataImporter).IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract);
var importers = new List<IUserDataImporter>();
foreach (var type in importerTypes)
{
if (enabled.Contains(type.Name))
{
var instance = (IUserDataImporter)Activator.CreateInstance(type)!;
importers.Add(instance);
}
}
foreach (var importer in importers)
{
Console.WriteLine($"运行插件:{importer.Name}");
var users = await importer.ImportAsync();
foreach (var user in users)
Console.WriteLine($"导入用户:{user.Name}");
}
问题 | 插件化的解决方式 |
|---|---|
多种来源,逻辑不同 | 每种来源写一个插件,各管各的 |
想停用某种来源 | 改一行配置就能停用 |
想新增来源 | 新增一个插件类,主程序不动 |
测试难,耦合重 | 插件可独立单元测试,完全解耦 |
发布风险高 | 插件可以单独部署、热更新 |
plugins/ 文件夹加载;Microsoft.Extensions.DependencyInjection 进行插件注入;System.Composition(MEF)或 Scrutor 进行自动发现;插件化并不是新概念,但在 .NET 项目中,很多人依旧用硬编码的方式在“拼命撑架子”。其实,你只要迈出第一步 —— 定义一个统一接口,把模块提取出来 —— 你就能立刻感受到:架构变得轻盈,维护不再恐惧。
如果你现在正面临“需求不断扩展”、“代码越写越乱”的局面,强烈建议你试试插件化。不用大改架构,也不难上手,就是把“每一块功能”做成“可以插拔”的模块。