C#中基于接口运行时返回类列表及动态GridData的OO最优实现咨询
嘿,针对你在C#里遇到的这两个问题——运行时返回类型列表的最佳方式,还有Screen类动态GridData的OOP优化,我来分享下实际项目里常用的最优思路~
一、运行时返回某类类型列表的最佳方式
如果是要获取实现特定接口/继承特定基类的所有类型,最常用的是结合反射,但要注意性能优化:
- 核心代码示例:
// 获取当前程序集中所有实现IGridData的非抽象、非接口类型 var targetTypes = Assembly.GetExecutingAssembly() .GetTypes() .Where(t => typeof(IGridData).IsAssignableFrom(t) && !t.IsAbstract && !t.IsInterface) .ToList();
- 性能优化:如果需要频繁调用,把结果缓存起来(比如用静态字段+静态构造函数初始化一次),避免每次都扫描程序集。
- 扩展:如果需要扫描多个程序集,比如引用的类库,替换
GetExecutingAssembly()为Assembly.Load("YourAssemblyName")即可。
二、Screen类动态GridData的OOP优化方案
你的需求是Screen类包含固定的GridConfiguration和动态的GridData(根据screenName变化),目前你让所有属性类都实现接口的思路是对的,但可以通过工厂模式+接口隔离来优化,避免不必要的接口方法实现:
1. 基础类型定义
先梳理清晰的类型结构,遵循单一职责:
// 标记接口:如果GridData之间没有公共行为,只需要统一类型,用空接口即可 public interface IGridData { } // 具体的GridData实现类,每个类对应一个screenName public class UserGridData : IGridData { // User屏幕特有的数据属性 public int UserId { get; set; } public string UserName { get; set; } } public class OrderGridData : IGridData { // Order屏幕特有的数据属性 public int OrderId { get; set; } public DateTime OrderDate { get; set; } } // 固定的GridConfiguration类,专注配置逻辑 public class GridConfiguration { public int PageSize { get; set; } public List<string> Columns { get; set; } = new(); // 其他固定配置属性 }
2. 用工厂模式封装动态创建逻辑
把根据screenName创建GridData的逻辑抽离到工厂类,避免业务代码里充斥if-else判断,符合开闭原则:
public class GridDataFactory { // 存储screenName到GridData创建逻辑的映射 private readonly Dictionary<string, Func<IGridData>> _creatorMap; public GridDataFactory() { _creatorMap = new Dictionary<string, Func<IGridData>> { { "UserScreen", () => new UserGridData() }, { "OrderScreen", () => new OrderGridData() } // 后续新增屏幕,只需要在这里添加一行,不需要修改其他代码 }; } public IGridData CreateGridData(string screenName) { if (_creatorMap.TryGetValue(screenName, out var creator)) { return creator(); } // 可根据需求返回默认实现或抛出异常 throw new ArgumentException($"未找到对应屏幕的GridData: {screenName}"); } }
3. 简化Screen类的使用
Screen类只需要持有统一的IGridData类型,通过工厂来初始化:
public class Screen { public GridConfiguration GridConfiguration { get; set; } public IGridData GridData { get; set; } // 静态创建方法,封装初始化逻辑 public static Screen Create(string screenName, GridConfiguration config, GridDataFactory factory) { return new Screen { GridConfiguration = config, GridData = factory.CreateGridData(screenName) }; } }
4. 解决你关于接口实现的困惑
如果之前你让所有GridData类都实现了一堆不必要的接口方法,那大概率是接口设计太宽泛了:
- 遵循接口隔离原则:只定义所有GridData都需要的公共行为,比如如果部分GridData需要导出功能,就单独定义
IExportableGridData接口,让需要的类实现,而不是强迫所有类都实现。 - 如果GridData之间完全没有公共行为,只是需要统一类型,用空的标记接口(比如上面的
IGridData)就足够了,不需要强制实现任何方法。
三、进阶优化:结合依赖注入(DI)
如果是在ASP.NET Core等使用DI的项目中,可以把GridData的创建交给DI容器,进一步解耦:
// 在Program.cs注册所有GridData实现 builder.Services.AddTransient<UserGridData>(); builder.Services.AddTransient<OrderGridData>(); builder.Services.AddTransient<GridDataFactory>(); // 修改工厂类,用DI获取实例 public class GridDataFactory { private readonly IServiceProvider _serviceProvider; private readonly Dictionary<string, Type> _typeMap; public GridDataFactory(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; _typeMap = new Dictionary<string, Type> { { "UserScreen", typeof(UserGridData) }, { "OrderScreen", typeof(OrderGridData) } }; } public IGridData CreateGridData(string screenName) { if (_typeMap.TryGetValue(screenName, out var type)) { return (IGridData)_serviceProvider.GetRequiredService(type); } throw new ArgumentException($"未找到对应屏幕的GridData: {screenName}"); } }
这样一来,GridData类的依赖(比如数据库上下文、服务等)可以通过DI自动注入,不需要手动创建实例,代码更健壮。
内容的提问来源于stack exchange,提问作者radhey_mishra
相关产品推荐
相关产品推荐

