Я пишу анализатор микроформатов в C # и ищу совет по рефакторингу. Вероятно, это первый «настоящий» проект, который я пытался сделать на C # в течение некоторого времени (я программирую почти исключительно на VB6 на своей повседневной работе), поэтому я чувствую, что этот вопрос может стать первым в серии; -)
Позвольте мне рассказать о том, что у меня есть, чтобы мой вопрос (надеюсь) имел смысл.
Прямо сейчас у меня есть один класс, MicroformatsParser
, делающий всю работу. Он имеет перегруженный конструктор, который позволяет передавать System.Uri
или string
, содержащий URI: при создании он загружает HTML-документ по указанному URI и загружает его в HtmlAgilityPack.HtmlDocument
для удобства манипулирования классом.
Базовый API работает так (или будет, когда я закончу код ...):
MicroformatsParser mp = new MicroformatsParser("http://microformats.org");
List<HCard> hcards = mp.GetAll<HCard>();
foreach(HCard hcard in hcards)
{
Console.WriteLine("Full Name: {0}", hcard.FullName);
foreach(string email in hcard.EmailAddresses)
Console.WriteLine("E-Mail Address: {0}", email);
}
Использование дженериков здесь является преднамеренным. Я черпал вдохновение в том, как работает библиотека микроформатов в Firefox 3 (и гем Ruby mofo
). Идея заключается в том, что синтаксический анализатор выполняет тяжелую работу (находя фактическое содержание микроформатов в HTML), а сами классы микроформатов (HCard
в приведенном выше примере) в основном предоставляют схему, которая сообщает анализатору, как обрабатывать данные, которые он обрабатывает. находит.
Код для класса HCard
должен прояснить это (обратите внимание, что это не полная реализация):
[ContainerName("vcard")]
public class HCard
{
[PropertyName("fn")]
public string FullName;
[PropertyName("email")]
public List<string> EmailAddresses;
[PropertyName("adr")]
public List<Address> Addresses;
public HCard()
{
}
}
Атрибуты, используемые здесь, используются синтаксическим анализатором для определения того, как заполнить экземпляр класса данными из документа HTML. Парсер делает следующее при вызове GetAll<T>()
:
- Проверяет, что тип
T
имеет атрибут ContainerName
(и он не пустой)
- Поиск в документе HTML всех узлов с атрибутом
class
, который соответствует ContainerName
. Назовите их «узлами контейнера».
- Для каждого контейнерного узла:
- Использует отражение для создания объекта типа
T
.
- Получить открытые поля (a
MemberInfo[]
) для типа T
через отражение
- Для каждого поля
MemberInfo
- Если поле имеет атрибут
PropertyName
- Получить значение соответствующего свойства микроформата из HTML
- Вставить значение, найденное в HTML, в поле (т.е. установить значение поля для объекта типа
T
, созданного на первом шаге)
- Добавьте объект типа
T
к List<T>
- Возвращает
List<T>
, который теперь содержит кучу микроформатных объектов
Я пытаюсь найти лучший способ реализовать шаг в полужирном . Проблема заключается в том, что Type
данного поля в классе микроформатов определяет не только какой узел искать в HTML, но также и как интерпретировать данные.
Например, возвращаясь к классу HCard
, определенному выше, свойство "email"
привязано к полю EmailAddresses
, которое является List<string>
. После того, как синтаксический анализатор находит все дочерние узлы "email"
родительского узла "vcard"
в HTML, он должен поместить их в List<string>
.
Более того, если я хочу, чтобы мой HCard
мог возвращать информацию о номере телефона, я, вероятно, хотел бы иметь возможность объявить новое поле типа List<HCard.TelephoneNumber>
(которое будет иметь свой собственный атрибут ContainerName("tel")
) чтобы хранить эту информацию, потому что в HTML может быть несколько элементов "tel"
, а формат "tel"
имеет свои собственные подчиненные свойства. Но теперь парсер должен знать, как поместить телефонные данные в List<HCard.TelephoneNumber>
.
Та же проблема относится к Float
S, DateTime
S, List<Float>
S, List<Integer>
S и т. Д.
Очевидный ответ - переключить анализатор на тип поля и выполнить соответствующие преобразования для каждого случая, но я хочу избежать гигантского оператора switch
. Обратите внимание, что я не планирую поддерживать синтаксический анализатор всеми возможными Type
в существовании, но я хочу, чтобы он обрабатывал большинство скалярных типов и их версии List<T>
вместе с возможностью распознавания других классов микроформатов ( так что класс микроформатов может быть составлен из других классов микроформатов).
Какой-нибудь совет, как лучше всего справиться с этим?
Поскольку синтаксический анализатор должен обрабатывать примитивные типы данных, я не думаю, что смогу добавить полиморфизм на уровне типов ...
Моей первой мыслью было использование перегрузки методов, поэтому у меня была бы серия перегрузок GetPropValue
, таких как GetPropValue(HtmlNode node, ref string retrievedValue)
, GetPropValue(HtmlNode, ref List<Float> retrievedValue)
и т. Д., Но мне интересно, есть ли лучший подход к этой проблеме.