如何优化不希望被直接实现的基础接口的暴露设计?
优化菜单选项API接口设计的思路
针对你提到的基础接口暴露、渲染时类型判断冗余,以及用户可能实现多个选项接口的问题,我分享几个实用的优化方案,都能保持API“只存描述信息+回调,不涉及渲染”的核心目标:
方案1:用抽象类替代基础接口,从语言层面限制无效实现
把IOption改成抽象类,这样用户无法直接实例化或实现它,必须通过继承它的派生接口/类来创建有效选项。同时把共享属性放在抽象类里,避免重复定义:
// 抽象基类:包含所有选项的共享属性,无法直接实现 public abstract class OptionBase { public string Id { get; init; } public string DisplayText { get; init; } // 其他共享属性,比如Disabled、Tooltip等 } // 具体选项接口,继承自抽象基类 public interface IOptionCheckbox : OptionBase { bool IsChecked { get; set; } void OnValueChanged(bool newValue); } // 客户端实现示例 public class MyUserCheckbox : IOptionCheckbox { public string Id { get; init; } public string DisplayText { get; init; } public bool IsChecked { get; set; } public void OnValueChanged(bool newValue) { // 客户端只需要关注自己的业务逻辑 Console.WriteLine($"用户切换了复选框 {Id},新值:{newValue}"); } }
这个方案的优点:
- 彻底避免用户直接实现“无效”的基础选项类型,编译器会直接报错
- 共享属性集中管理,减少重复代码
- 渲染时可以用模式匹配替代硬转型,代码更简洁:
foreach (var option in group.Options) { if (option is IOptionCheckbox checkbox) { // 渲染复选框 } else if (option is IOptionRadioButton radio) { // 渲染单选按钮 } }
方案2:使用访问者模式,消除渲染时的类型判断
如果想彻底摆脱类型判断的冗余代码,同时增强扩展性,访问者模式是绝佳选择。它把“判断类型并处理”的逻辑从循环里抽离出来,交给专门的访问者类:
步骤1:定义访问者接口和修改基础选项接口
// 访问者接口:每个选项类型对应一个Visit方法 public interface IOptionVisitor { void VisitCheckbox(IOptionCheckbox checkbox); void VisitRadioButton(IOptionRadioButton radioButton); // 新增选项类型时,只需在这里添加对应的Visit方法 } // 基础选项接口:加入Accept方法,接收访问者 public interface IOption { string Id { get; } string DisplayText { get; } void Accept(IOptionVisitor visitor); } // 具体选项接口 public interface IOptionCheckbox : IOption { bool IsChecked { get; set; } void OnValueChanged(bool newValue); }
步骤2:客户端实现选项类
public class MyUserCheckbox : IOptionCheckbox { public string Id { get; init; } public string DisplayText { get; init; } public bool IsChecked { get; set; } public void OnValueChanged(bool newValue) { // 客户端自定义逻辑 } // 实现Accept方法,把自身传递给访问者的对应方法 public void Accept(IOptionVisitor visitor) { visitor.VisitCheckbox(this); } }
步骤3:渲染器实现访问者接口
public class MenuRenderer : IOptionVisitor { public void VisitCheckbox(IOptionCheckbox checkbox) { // 直接拿到具体类型,无需转型,专注渲染逻辑 Console.WriteLine($"渲染复选框:[{(checkbox.IsChecked ? "✓" : " ")}] {checkbox.DisplayText}"); } public void VisitRadioButton(IOptionRadioButton radioButton) { // 渲染单选按钮的逻辑 } }
使用方式
var renderer = new MenuRenderer(); foreach (var option in group.Options) { option.Accept(renderer); }
这个方案的优点:
- 完全消除渲染时的类型判断,代码更干净
- 新增选项类型时,只需修改访问者接口和渲染器,符合开闭原则
- 从语言层面避免用户实现多个选项接口(因为
Accept方法会冲突,编译器报错) - 客户端依然只需要关注自己的回调逻辑,完全不接触渲染细节
方案3:标记接口+代码分析器(进阶限制)
如果坚持要用纯接口设计,可以把IOption改成无任何成员的标记接口,然后通过自定义Roslyn代码分析器禁止用户直接实现IOption,只能实现它的派生接口。不过这个方案需要额外开发分析器,适合对API严谨性要求极高的场景,一般前两个方案足够覆盖需求。
内容的提问来源于stack exchange,提问作者Aaron
相关产品推荐
相关产品推荐

