You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何优化不希望被直接实现的基础接口的暴露设计?

优化菜单选项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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 09:33:07