静态定义派生类的类型的选项

问题描述 投票:2回答:2

我正在开发一个框架,其中从框架的抽象类继承的类需要能够指定它在调用DoStuff()时可以接受的选项的模式。

我开始使用这样的抽象GetOptionsSchema()方法:

public abstract class Widget
{
    public abstract OptionsSchema GetOptionsSchema();
    public abstract void DoStuff(Options options);
}

然后,其他开发人员将通过创建自定义Widget类型来扩展我的框架:

public abstract class FooWidget: Widget
{
    public overide DoStuff(Options options)
    {
        //Do some FooWidget stuff
    }

    public overide OptionsSchema GetOptionsSchema()
    {
        //Return options for FooWidget
    }
}

这有效但需要框架创建每个Widget类型的实例以确定它们接受的选项模式,即使它不需要实际上具有任何这些类型的DoStuff()。

最后,我希望能够直接从System.Type确定特定Widget类型的选项模式。我会创建一个自定义的OptionsSchema属性,但构造这些模式更复杂,然后在属性的构造函数中有意义。它需要在一个方法中发生。

我已经看到其他框架通过创建一个自定义属性来解决类似问题,该属性通过名称标识静态方法或属性。例如,NUnit中的TestCaseSource属性。

以下是此选项的外观:

public abstract class Widget
{
    public abstract void DoStuff(Options options);
}

[OptionsSchemaSource(nameof(GetOptionsSchema))]
public abstract class FooWidget: Widget
{
    public overide DoStuff(Options options)
    {
        //Do some FooWidget stuff
    }

    public static OptionSchema GetOptionsSchema()
    {
        //Return options for FooWidget
    }
}

我喜欢OptionsSchemaSource属性如何直接从System.Type获取选项模式,但对于创建自定义Widget类型的其他开发人员来说,这似乎更难以发现。

使用抽象方法,另一个Widget开发人员知道他们必须覆盖GetOptionSchema(),因为他们的代码不会编译。使用OptionsSchemaSource属性,我能做的最好的事情就是希望人们阅读我的文档并让框架在遇到带有OptionsSchemaSource属性的Widget时在运行时抛出异常。

是否有替代/更好/推荐的方法?

c# .net attributes static-methods
2个回答
1
投票

你几乎已经知道所有感兴趣的事情来判断什么是最好的方法。

如前所述,您不能在类型上定义静态接口,因此无法确保强制新的开发人员添加属性。

所以,你确定的两个替代方案是我能想到的唯一两个。现在,让我们做一个利弊,并尝试磨砺它们。

Attribute

您可以减少确保开发人员使用有意义的错误消息将属性放在类上的痛苦。我会说你应该完全基于属性而不是继承来管理类的发现。如果使用Attributes管理所有内容,则无需从Widget继承。这是专业人士,因为现在每个人都可以继承,如果它是可取的,并重新实施,如果它是首选。

可以理解的是,可发现性的实现将更加复杂:您需要在启动时使用反射,获取MethodInfo,检查方法是否具有正确的签名,在案例中给出正确的错误并调用方法根据需要拆箱结果。

想一想:你想要一个静态方法,因为你不需要实例化一个类型的Widget实例,但实际上实例化一个新的Widget可能不是什么大问题。

Abstract class

好吧,你对你的开发人员强制执行一个继承链,这可能是好的,必要的或完全可选的(你判断),但你获得了自我记录的经验。

显而易见的是,在启动时你需要为你发现的每个派生类型实例化一个Widget,但与组装扫描和类型检查以及methodinfo发现和通过反射的方法调用相比,这很可能是花生。丑陋?的种类。效率低下?没那么多。它是最终用户看不到的代码。

IMHO

在设计框架时,我发现一个很好的权衡,在框架中放入一些“丑陋”的代码,如果这意味着使用库的每个实现都会更好一点。

总而言之,如果您正在设计一个您想要灵活且可被发现的库,那么您应该期望开发人员至少阅读快速入门指南。如果他们可以在5分钟内读取一点信息(“扩展基类”或“添加单个或几个属性”)并且该单个位为他们提供了发现小部件注册的每个方面的方向,我将是好的:你真的不能比这更好。

我的电话:我会带着一个小小的警告去抽象的班级路线。我真的不喜欢有强制基类。因此,我将在启动时基于接口IWidget组织发现,包含GetOptionsSchema方法,并且需要使用小部件的一切(可能是DoStuff方法,但很可能是其他的东西)。在启动时,您搜索非抽象的接口实现,并且您很高兴。

如果,并且只有当你真正需要的那一个是字符串或其他类似的简单类型时,我还需要一个额外的属性。

[OptionsSchemaName("http://something")]
public class MyWidget : WidgetBase
{
    public overide DoStuff(Options options)
    {
        //Do some FooWidget stuff
    }

    public static OptionSchema GetOptionsSchema()
    {
        //Return options for FooWidget
    }
}

然后,您的类型发现基础结构可以搜索非抽象的IWidgets,并在启动时像the type MyWidget is lacking an OptionsSchemaName attribute. Every implementation of IWidget must define one. See http://mydocs for information一样抛出有意义的错误。

砰!搞定了!


1
投票

目前无法在编译时强制执行该属性;这对你的用例来说非常理想。也不可能有abstract static方法,或者在接口中指定静态方法;所以除了通过抽象类或接口强制实例方法(这将需要访问类型的实例)之外,没有办法确保该方法实际上在编译时存在。

我会考虑属性的想法 - 期望开发人员阅读文档是不合理的;即使重写了抽象方法,开发人员也需要知道如何在重写方法中构建OptionSchema - 返回文档!

© www.soinside.com 2019 - 2024. All rights reserved.