У меня странная дизайнерская ситуация, с которой я никогда раньше не сталкивался ... Если бы я использовал Objective-C, я бы решил ее с помощью категорий, но я должен использовать C# 2.0.
Во-первых, немного предыстории. В этой библиотеке классов у меня есть два уровня абстракции. Нижний уровень реализует архитектуру подключаемых модулей для компонентов, которые сканируют контент (извините, не могу быть более конкретным, чем это). Каждый плагин будет сканировать по-своему, но плагины также могут различаться в зависимости от того, какой тип контента они принимают. Я не хотел предоставлять Generics через интерфейс плагина по разным причинам, не имеющим отношения к этому обсуждению. Итак, я получил интерфейс IScanner и производный интерфейс для каждого типа контента.
Верхний уровень - это удобная оболочка, которая принимает составной формат содержимого, который содержит различные части. Разным сканерам потребуются разные части составного контента, в зависимости от того, какой тип контента их интересует. Следовательно, мне нужно иметь логику, специфичную для каждого интерфейса, производного от IScanner, который анализирует составной контент в поисках соответствующей части, которая требуется.
Один из способов решить эту проблему - просто добавить еще один метод в IScanner и реализовать его в каждом подключаемом модуле. Однако вся суть двухуровневого дизайна заключается в том, что самим плагинам не нужно знать о составном формате. Решить эту проблему методом грубой силы можно с помощью типовых тестов и понижающих преобразований на верхнем уровне, но их нужно будет тщательно поддерживать, поскольку в будущем будет добавлена поддержка новых типов контента. Шаблон посетителя также будет неудобным в этой ситуации, потому что на самом деле существует только один посетитель, но количество различных типов посетителей со временем будет только увеличиваться (т. Е. Это противоположные условия, для которых подходит посетитель). Кроме того, двойная отправка кажется излишеством, когда на самом деле все, что мне нужно, - это захватить одиночную отправку IScanner!
Если бы я использовал Objective-C, я бы просто определил категорию для каждого интерфейса, производного от IScanner, и добавил бы туда метод parseContent. Категория будет определена на верхнем уровне, поэтому подключаемые модули не нужно будет менять, одновременно избегая необходимости в типовых тестах. К сожалению, методы расширения C# не будут работать, потому что они в основном статичны (т.е. привязаны к типу ссылки во время компиляции, используемой на сайте вызова, а не подключены к динамической отправке, как Obj-C Categories). Не говоря уже о том, что я должен использовать C# 2.0, поэтому мне даже недоступны методы расширения. :-П
Итак, есть ли чистый и простой способ решить эту проблему на C#, похожий на то, как ее можно решить с помощью категорий Objective-C?
Обновлено: некоторый псевдокод, помогающий прояснить структуру текущего дизайна:
interface IScanner
{ // Nothing to see here...
}
interface IContentTypeAScanner : IScanner
{
void ScanTypeA(TypeA content);
}
interface IContentTypeBScanner : IScanner
{
void ScanTypeB(TypeB content);
}
class CompositeScanner
{
private readonly IScanner realScanner;
// C-tor omitted for brevity... It takes an IScanner that was created
// from an assembly-qualified type name using dynamic type loading.
// NOTE: Composite is defined outside my code and completely outside my control.
public void ScanComposite(Composite c)
{
// Solution I would like (imaginary syntax borrowed from Obj-C):
// [realScanner parseAndScanContentFrom: c];
// where parseAndScanContentFrom: is defined in a category for each
// interface derived from IScanner.
// Solution I am stuck with for now:
if (realScanner is IContentTypeAScanner)
{
(realScanner as IContentTypeAScanner).ScanTypeA(this.parseTypeA(c));
}
else if (realScanner is IContentTypeBScanner)
{
(realScanner as IContentTypeBScanner).ScanTypeB(this.parseTypeB(c));
}
else
{
throw new SomeKindOfException();
}
}
// Private parsing methods omitted for brevity...
}
Обновлено: Чтобы уточнить, я уже много думал об этом дизайне. У меня есть много причин, большинством из которых я не могу поделиться, почему так оно и есть. Я еще не принял никаких ответов, потому что, хотя они и интересны, они уклоняются от исходного вопроса.
Дело в том, что в Obj-C я смог решить эту проблему просто и элегантно. Вопрос в том, могу ли я использовать ту же технику в C#, и если да, то как? Я не против искать альтернативы, но, честно говоря, я задавал не этот вопрос. :)





Я попытаюсь... ;-)
Если в вашей системе есть этап, когда вы заполняете свой «каталог» объектов IScanner, вы можете подумать об украшении ваших IScanner с помощью атрибута, указывающего, какой из Part им интересен. Затем вы можете сопоставить эту информацию и запустить сканирование вашего Composite с помощью карта.
Это не полный ответ: если у меня будет немного времени, я постараюсь уточнить ...
Обновлено: немного псевдокода, чтобы поддержать мое запутанное объяснение
public interface IScanner
{
void Scan(IPart part);
}
public interface IPart
{
string ID { get; }
}
[ScannedPart("your-id-for-A")]
public class AlphaScanner : IScanner
{
public void Scan(IPart part)
{
throw new NotImplementedException();
}
}
[ScannedPart("your-id-for-B")]
public class BetaScanner : IScanner
{
public void Scan(IPart part)
{
throw new NotImplementedException();
}
}
public interface IComposite
{
List<IPart> Parts { get; }
}
public class ScannerDriver
{
public Dictionary<string, IScanner> Scanners { get; private set; }
public void DoScan(IComposite composite)
{
foreach (IPart part in composite.Parts)
{
IScanner scanner = Scanners[part.ID];
scanner.Scan(part);
}
}
}
Не принимайте это как есть: это для объяснения.
Обновлено: ответ на комментарии полковника Кернела. Я рада, что тебе это интересно. :-) В этом простом наброске кода отражение должно быть задействовано только во время инициализации Словаря (или когда это необходимо), и на этом этапе вы можете "принудительно" обеспечить присутствие атрибута (или даже использовать другие способы сопоставления сканеров и частей). Я говорю «принудительно», потому что, даже если это не ограничение времени компиляции, я думаю, вы запустите свой код Хотя бы один раз, прежде чем вводить его в производство ;-), так что при необходимости это может быть ограничение времени выполнения. Я бы сказал, что это что-то (очень-очень легкое) похожее на MEF или другие подобные фреймворки. Только мои 2 цента.
Я не думал об использовании атрибутов ... Однако это не решает проблемы. Он просто преобразует типовой тест в проверку атрибута на основе отражения и не заставляет классы сканера включать атрибут. Но интересный подход.
См. Последнее добавленное мною "правка" для возможного ответа на ваш комментарий. :-)
В моем случае нет централизованного управления для регистрации плагинов. Информация об их конфигурации (включая полное имя типа) хранится в БД, и они загружаются по запросу. Я не могу это изменить. Таким образом, проверка атрибутов будет происходить слишком поздно для моих целей.
Если конфигурация находится в БД, это все еще может быть в порядке (метаданные, т. Е. Сопоставление с атрибутами, - это лишь один из вариантов), но я думаю, что не могу быть полезным без дополнительных деталей, и я не знаю, хотите ли вы раскрыть их ... ;-)
Можете ли вы указать на хороший ресурс, где можно узнать больше о категориях Objective-C?