Как спроектировать мой C # jQuery API так, чтобы он не мешал использовать? - PullRequest
5 голосов
/ 04 ноября 2010

Я делаю jquery клон для C #. Сейчас я настроил его так, что каждый метод является методом расширения в IEnumerable<HtmlNode>, поэтому он хорошо работает с существующими проектами, которые уже используют HtmlAgilityPack. Я думал, что смогу обойтись без сохранения состояния ... однако затем я заметил, что в jQuery есть два метода .andSelf и .end, которые "выталкивают" самые последние сопоставленные элементы из внутреннего стека. Я могу имитировать эту функцию, если я изменю свой класс так, чтобы он всегда работал с объектами SharpQuery вместо перечислимых, но проблема все еще остается.

С JavaScript вы получаете HTML-документ автоматически, но при работе в C # вы должны явно загружать его, и вы можете использовать более одного документа, если хотите. Похоже, что когда вы вызываете $('xxx'), вы по сути создаете новый объект jQuery и начинаете заново с пустого стека. В C # вам бы этого не хотелось, потому что вы не хотите перезагружать / повторно загружать документ из Интернета. Поэтому вместо этого вы загружаете его один раз либо в объект SharpQuery, либо в список HtmlNodes (вам просто нужно DocumentNode, чтобы начать работу).

В документах jQuery они приводят этот пример

$('ul.first').find('.foo')
  .css('background-color', 'red')
.end().find('.bar')
  .css('background-color', 'green')
.end();

У меня нет метода инициализации, потому что я не могу перегрузить оператор (), поэтому вместо этого вы просто начинаете с sq.Find(), который работает с корнем документа, по сути, делая то же самое. Но тогда люди попытаются написать sq.Find() в одной строке, а затем sq.Find() где-нибудь в будущем и (по праву) ожидать, что он снова будет работать с корнем документа ... но если я продолжу состояние, то вы только что изменили контекст после первого вызова.

Итак ... как мне разработать свой API? Должен ли я добавить еще один Init метод, с которого все запросы должны начинаться с сброса стека (но затем, как мне заставить их начать с этого?), Или добавить Reset(), который они должны вызывать в конце своей строки ? Должен ли я вместо этого перегрузить [] и сказать им, чтобы начать с этого? Должен ли я сказать «забудь, никто не использует эти сохраненные государством функции?»

По сути, как бы вы хотели, чтобы этот пример jQuery был написан на C #?

  1. sq["ul.first"].Find(".foo") ...
    Ошибки: злоупотребляет свойством [].

  2. sq.Init("ul.first").Find(".foo") ...
    Недостатки: ничто действительно не заставляет программиста начинать с Init, если я не добавлю какой-нибудь странный «инициализированный» механизм; пользователь может попробовать начать с .Find и не получить ожидаемого результата. Кроме того, Init и Find в значительной степени идентичны, за исключением того, что первый тоже сбрасывает стек.

  3. sq.Find("ul.first").Find(".foo") ... .ClearStack()
    Недостатки: программист может забыть очистить стек.

  4. Не могу этого сделать.
    end() не реализовано.

  5. Используйте два разных объекта.
    Возможно, используйте HtmlDocument в качестве основы, с которой должны начинаться все запросы, а затем каждый последующий метод возвращает объект SharpQuery, который можно объединить в цепочку. Таким образом, HtmlDocument всегда поддерживает начальное состояние, но объекты SharpQuery могут иметь разные состояния. К сожалению, это означает, что мне нужно реализовать кучу вещей дважды (один раз для HtmlDocument, один раз для объекта SharpQuery).

  6. new SharpQuery(sq).Find("ul.first").Find(".foo") ...
    Конструктор копирует ссылку на документ, но сбрасывает стек.

Ответы [ 2 ]

4 голосов
/ 07 ноября 2010

Я думаю, что основным камнем преткновения, с которым вы здесь сталкиваетесь, является то, что вы пытаетесь избежать необходимости иметь только один объект SharpQuery для каждого документа. Это не так, как работает JQuery; в общем, объекты jQuery неизменны. Когда вы вызываете метод, который изменяет набор элементов (например, find или end или add), он не изменяет существующий объект, но возвращает новый:

var theBody = $('body');
// $('body')[0] is the <body>
theBody.find('div').text('This is a div');
// $('body')[0] is still the <body>

(см. документацию end для получения дополнительной информации)

SharpQuery должен работать так же. После создания объекта SharpQuery с документом вызовы методов должны возвращать новые объекты SharpQuery, ссылающиеся на другой набор элементов того же документа. Например:

var sq = SharpQuery.Load(new Uri("http://api.jquery.com/category/selectors/"));
var header = sq.Find("h1"); // doesn't change sq
var allTheLinks = sq.Find(".title-link") // all .title-link in the whole document; also doesn't change sq
var someOfTheLinks = header.Find(".title-link"); // just the .title-link in the <h1>; again, doesn't change sq or header

Преимущества этого подхода несколько. Поскольку sq, header, allTheLinks и т. Д. Являются одним и тем же классом, у вас есть только одна реализация каждого метода. Тем не менее каждый из этих объектов ссылается на один и тот же документ, поэтому у вас нет нескольких копий каждого узла, и изменения узлов отражаются в каждом SharpQuery объекте в этом документе (например, после allTheLinks.text("foo"), someOfTheLinks.text() == "foo".) .

Реализация end и других основанных на стеке манипуляций также становится легкой. Поскольку каждый метод создает новый, отфильтрованный SharpQuery объект из другого, он сохраняет ссылку на этот родительский объект (allTheLinks до header, header до sq). Тогда end так же просто, как вернуть новый SharpQuery, содержащий те же элементы, что и родительский, например:

public SharpQuery end()
{
    return new SharpQuery(this.parent.GetAllElements());
}

(или, тем не менее, ваш синтаксис встряхивает.)

Я думаю, что этот подход даст вам наиболее jQuery-подобное поведение с довольно простой реализацией. Я определенно буду следить за этим проектом; это отличная идея.

0 голосов
/ 07 ноября 2010

Я бы склонялся к варианту по варианту 2. В jQuery $ () - это вызов функции. C # не имеет глобальных функций, статический вызов функции является ближайшим. Я бы использовал метод, который указывает, что вы создаете оболочку, как ..

SharpQuery.Create("ul.first").Find(".foo")

Я не буду беспокоиться о сокращении SharpQuery до sq, поскольку intellisense означает, что пользователям не нужно будет печатать все это целиком (а если у них есть более резкое перфорирование, то в любом случае ему нужно всего лишь вводить SQ).

...