是否有更好的设计来公开您不想直接实现的基本接口?

我正在创建一个 API,允许某人构建带有可选选项的菜单。API 库本身没有任何渲染逻辑,只有描述选项所需的信息。这样,此 API 的使用者可以将其添加为依赖项,渲染器库可以将其添加为依赖项。然后渲染器可以根据消费者创建的对象渲染菜单。


它是这样的:


public interface IOptionGroup

{

    List<IOption> Options { get; }

}


public interface IOption

{

    //Some Shared attributes for all Options

}


public interface IOptionCheckbox : IOption

{

    // Some checkbox specific attributes

    void OnValueChanged(bool newValue);

}

暴露基础接口:


拥有基本 IOption 接口很好,因为它拥有所有不同 Option 类型的所有共享属性,但只有 API 用户实现从 IOption 派生的接口才有意义。如果他们自己实现了 IOption 并将其作为选项之一传回,则将没有足够的信息来实际将有效的选项绘制到菜单中。


渲染期间向上转换:


此外,这种设计迫使我向上转换接口以确定要呈现的 Option 类型(这感觉不对)。另外,如果他们实现了多个 Option 接口呢?!?


foreach(IOption option in group.Options) 

{

    if (option is IOptionCheckbox) 

    {

        // Render a Checkbox

    } else if (option is ...)

    {

       // Render a ...

    }

}

用例使其更清晰:


我希望客户端能够实现描述菜单项的接口,而无需了解有关呈现它们的任何信息。对于不同的选项,他们得到不同的回调:


示例客户端代码:


public class MyCheckBox : IOptionCheckbox

{

    void OnValueChanged(bool newValue)

    {

        // Do something specific here when user checks/unchecks option

    }

}

客户端只需担心单击复选框时要执行的操作。我不能在 IOption 接口中放置渲染器,因为这需要客户端自己实现重渲染器。


Smart猫小萌
浏览 149回答 1
1回答

潇湘沐

我还没有想出一个完美的方法来做我想做的事,但我会将我的答案分成两个选项,每个选项都有优缺点。如果您正在为自己的类似问题寻求解决方案,您可能希望选择选项 1。选项 1:良好的接口这里的主要问题(正如@Servy 指出的)是接口应该真正包含执行其预期目的所需的所有操作。上面例子的样子可能是这样的:public interface IRenderer{&nbsp; &nbsp; void Draw(/* Params needed to draw */);}public interface IOption{&nbsp; &nbsp; //Some Shared attributes for all Options&nbsp; &nbsp; IRenderer Renderer { get; }}然后,呈现库可以提供(可能是抽象的)接口的实现,这些实现是从 IOption 派生的,客户端可以使用这些实现来呈现选项,或者继承并覆盖或简单地实例化和使用。渲染器代码:public abstract class RendererOptionCheckbox : IOptionCheckbox{&nbsp; &nbsp; public IRenderer Renderer => new CheckboxRender();&nbsp; &nbsp; public abstract void OnValueChanged(bool newValue);}客户代码:public class MyCheckBox : RendererOptionCheckbox{&nbsp; &nbsp; void OnValueChanged(bool newValue)&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; // Do something specific here when user checks/unchecks option&nbsp; &nbsp; }}优点:无需向上转换以渲染 IOption 列表,因为每个选项都知道如何渲染自身暴露基本 IOption 接口没有问题,因为如果客户端尝试使用它,则必须定义渲染器客户端可以使用自定义呈现逻辑注入他们自己的自定义 IOption缺点:客户端可以使用自定义渲染逻辑注入他们自己的自定义 IOption(这是一把双刃剑,你可能不想要这个)客户端需要实例化直接从渲染器继承的对象,使得实际渲染器和客户端之间的耦合比我想要的更紧密。选项 2:记录您的问题只需按照原始问题中的描述编写代码,使用向上转换来执行渲染,如果直接实现 IOption 则抛出详细的异常,并提供有效使用与无效使用类的良好文档(如@DanWilson 建议的那样)。优点:从客户的角度清洁界面(看不到渲染逻辑)客户端可以使用接口而不必担心要实例化或继承哪些相关的渲染器特定类无需更改客户端代码即可轻松交换渲染器缺点:向上转换以呈现 IOptions 列表(糟糕!)IOption 已公开但不能直接使用(令人困惑)
打开App,查看更多内容
随时随地看视频慕课网APP