Статически определяемые параметры для типа производного класса - PullRequest
0 голосов
/ 14 февраля 2019

Я разрабатываю среду, в которой класс, унаследованный от абстрактного класса платформы, должен иметь возможность указывать схему для параметров, которые он может принимать при вызове DoStuff ().

Iначалось с абстрактного метода GetOptionsSchema (), например:

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

Другие разработчики затем расширили бы мою инфраструктуру, создавая пользовательские типы виджетов:

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.Я бы создал собственный атрибут OptionsSchema, но построение этих схем сложнее, чем это имеет смысл сделать в конструкторе атрибута.Это должно происходить в методе.

Я видел, как другие фреймворки решают подобные проблемы, создавая собственный атрибут, который идентифицирует статический метод или свойство по имени.Например, атрибут TestCaseSource в NUnit.

Вот как может выглядеть этот параметр:

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, но это также кажется гораздо менее доступным для других разработчиков, создающих собственные типы виджетов.

С помощью абстрактного метода другой разработчик виджетов знает, что он должен переопределить GetOptionSchema (), потому что их код не скомпилируется иначе.С атрибутом OptionsSchemaSource лучше всего было бы надеяться, что люди прочитают мою документацию и заставят инфраструктуру выдать исключение во время выполнения, если он встретит виджет без атрибута OptionsSchemaSource.

Есть ли альтернатива /лучше / рекомендуемый подход к этому?

Ответы [ 2 ]

0 голосов
/ 14 февраля 2019

Вы уже почти все знаете, чтобы судить о том, какой подход лучше.

Как уже упоминалось, у вас не может быть статических интерфейсов, определенных для вашего типа, поэтому вы не можете гарантировать, что новый разработчик принудительно добавил для добавления атрибута.

Итак, две выбранные вами альтернативы - единственные две, о которых я могу подумать.Теперь давайте сделаем плюсы и минусы и попытаемся отточить их.

Атрибут

Вы можете уменьшить боль, гарантируя, что разработчики помещают атрибуты в классы с осмысленными сообщениями об ошибках.Я бы сказал, что вы должны управлять обнаружением классов, основанных исключительно на Атрибутах, а не на наследовании.Если вы управляете всем с помощью атрибутов, вам не нужно наследовать от виджета.Это профессионал, потому что теперь каждый может наследовать, если это желательно, и повторно внедрять, если это предпочтительно.

Дело в том, что реализация обнаруживаемости будет более сложной: вам нужно будет использовать отражение при запускеполучите MethodInfo, убедитесь, что метод имеет правильную сигнатуру, дайте правильные ошибки в случае, и при необходимости вызовите метод, распаковывая результат.

Подумайте об этом: вам нужен статический метод, потому что вы не используетене нужно создавать экземпляр одного типизированного экземпляра Widget, но на самом деле создание нового Widget вполне может быть не таким уж большим делом., что может быть в порядке, необходимо или совершенно необязательно (судите сами), но вы получаете самодокументируемый опыт.

Очевидным доводом является то, что при запуске вам нужно создать экземпляр виджета для каждого обнаруженного вами производного типа, ноэто вполне может быть арахис по сравнению со сборкой сканирования и проверки типа и методаОбнаружение информации и вызовы методов через рефлексию.Гадкий?Вид.Неэффективное?Не так много.И это код, который невидим для вашего конечного пользователя.

ИМХО

Я нахожу весьма неплохой компромисс при разработке фреймворка, чтобы поместить некоторый "уродливый" код внутрь фреймворк, если это означает, что каждая реализация, использующая библиотеку, будет даже немного лучше.

В общем, если вы разрабатываете библиотеку, которую вы хотите быть гибкой и доступной для обнаружения, выследует ожидать, что разработчик прочитает хотя бы краткое руководство по началу работы.Если они смогут прочитать за 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
    }
}

Затем ваша инфраструктура обнаружения типов может искать неабстрактные IWidget s и выдавать значимую ошибку.прямо при запуске как the type MyWidget is lacking an OptionsSchemaName attribute. Every implementation of IWidget must define one. See http://mydocs for information.

Bang!Пригвоздил это!

0 голосов
/ 14 февраля 2019

В настоящее время невозможно применить атрибут во время компиляции;это было бы идеально для вашего случая использования.Также невозможно иметь метод abstract static или статический метод, указанный в интерфейсе;таким образом, нет никакого способа убедиться, что метод действительно существует во время компиляции, за исключением принудительного применения метода экземпляра через абстрактный класс или интерфейс (который потребует доступа к экземпляру типа).

Я бы пошелс идеей атрибута - вполне разумно ожидать, что разработчики будут читать документацию;даже при переопределении абстрактного метода разработчику необходимо знать, как создать OptionSchema в переопределенном методе - назад к документации!

...