Cómo diseñar mi API C # jQuery tal que no es confuso para usar?
-
28-09-2019 - |
Pregunta
hacer un clon de jQuery para C #. En este momento lo tengo configurado para que cada método es un método de extensión en IEnumerable<HtmlNode>
por lo que funciona bien con los proyectos existentes que ya están utilizando HtmlAgilityPack
. Pensé que podría salir de allí sin preservar el estado ... sin embargo, entonces me di cuenta de jQuery tiene dos métodos .andSelf
y .end
la que "pop" los elementos más recientemente emparejados fuera una pila interna. Puedo imitar esta funcionalidad si cambio de clase de manera que siempre opera sobre objetos SharpQuery en lugar de enumerables, pero todavía hay un problema.
Con Javascript, que está dado el documento HTML de forma automática, pero cuando se trabaja en C # tiene que cargar de forma explícita, y se puede utilizar más de un documento si querías. Parece que cuando se llama a $('xxx')
que está esencialmente creando un nuevo objeto jQuery y empezar de nuevo con una pila vacía. En C #, que no le gustaría hacer eso, ya que no desea volver a cargar / refetch el documento de la web. Así que en lugar, se carga una vez, ya sea en un objeto SharpQuery, o en una lista de HtmlNodes (sólo tiene la DocumentNode para empezar).
En los docs jQuery, dan este ejemplo
$('ul.first').find('.foo')
.css('background-color', 'red')
.end().find('.bar')
.css('background-color', 'green')
.end();
No tengo un método de inicialización porque no puedo sobrecargar el operador ()
, por lo que acaba de empezar con sq.Find()
lugar, que opera en la raíz del documento, haciendo esencialmente la misma cosa. Pero la gente luego van a tratar de sq.Find()
escribir en una sola línea, y en algún lugar y luego sq.Find()
en el camino, y (con razón) esperan que operan en la raíz del documento de nuevo ... pero si estoy mantener el estado, entonces han modificado el contexto simplemente después de la primera llamada.
Así que ... ¿cómo debo diseñar mi API? Agrego otro método Init
que todas las consultas deben comenzar con que restablece la pila (pero entonces ¿cómo puedo obligarlos a empezar con eso?), O añadir un Reset()
que tienen que llamar al final de su línea? Puedo sobrecargar el []
lugar y decirles a empezar con eso? Por qué digo "olvidarse de él, nadie usa esas funciones conservada por el estado de todos modos?"
Básicamente, ¿cómo le gustaría que el ejemplo jQuery para ser escrito en C #?
-
sq["ul.first"].Find(".foo") ...
Desventajas:. Abusa de la propiedad[]
-
sq.Init("ul.first").Find(".foo") ...
Desventajas: En realidad, nada obliga al programador para comenzar con Init, a menos que añado algún mecanismo raro "inicializado"; usuario podría intentar el inicio de la.Find
y no conseguir el resultado que esperaba. Además,Init
yFind
son de todos modos más o menos idénticos, excepto los antiguos restablece la pila también. -
sq.Find("ul.first").Find(".foo") ... .ClearStack()
Desventajas:. Programador puede olvidarse de limpiar la pila -
No puede hacerlo.
end()
no se han aplicado. -
Usar dos objetos diferentes.
Tal vez utilizarHtmlDocument
como la base de que todas las consultas deben empezar, y luego cada método devuelve un objeto a partir de entoncesSharpQuery
que puede ser de cadena. De esta forma elHtmlDocument
siempre mantiene el estado inicial, pero los objetosSharpQuery
puede tener diferentes estados. Esto, lamentablemente, significa que tengo que poner en práctica un montón de cosas dos veces (una vez para HtmlDocument, una vez para el objeto SharpQuery). -
new SharpQuery(sq).Find("ul.first").Find(".foo") ...
Las copias del constructor una referencia al documento, pero se restablece la pila.
Solución
Creo que el principal obstáculo que se está ejecutando en este caso es que usted está tratando de salirse con sólo tener un objeto SharpQuery
para cada documento. Así no es como funciona jQuery; en general, los objetos jQuery son inmutables. Cuando se llama a un método que cambia el conjunto de elementos (como find
o end
o add
), que no altera el objeto existente, pero devuelve una nueva:
var theBody = $('body');
// $('body')[0] is the <body>
theBody.find('div').text('This is a div');
// $('body')[0] is still the <body>
(véase el documentación de end
para obtener más información)
SharpQuery debe funcionar de la misma manera. Una vez que se crea un objeto SharpQuery con un documento, las llamadas de método debe devolver los objetos nuevos SharpQuery
, hace referencia a un conjunto diferente de elementos de un mismo documento. Por ejemplo:
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
Los beneficios de este enfoque son varias. Debido sq
, header
, allTheLinks
, etc, todos ellos de la misma clase, que sólo tiene una aplicación de cada método. Sin embargo, cada uno de estos objetos referencias del mismo documento, por lo que no tiene varias copias de cada nodo, y los cambios en los nodos se reflejan en cada objeto SharpQuery
en ese documento (por ejemplo, después allTheLinks.text("foo")
, someOfTheLinks.text() == "foo"
.).
Ejecución end
y las otras manipulaciones basados ??en la pila también se hace fácil. Como cada método crea un nuevo objeto SharpQuery
filtrada de otro, conserva una referencia a ese objeto padre (allTheLinks
a header
, header
a sq
). Entonces end
es tan simple como el retorno de una nueva SharpQuery
que contiene los mismos elementos que los padres, como:
public SharpQuery end()
{
return new SharpQuery(this.parent.GetAllElements());
}
(o como su sintaxis sacude.)
Creo que este enfoque le dará el más jQuery-como el comportamiento, con una aplicación bastante fácil. Definitivamente, voy a ser de mantenimiento de un ojo en este proyecto; que es una gran idea.
Otros consejos
I se inclinaría hacia una variante de la opción 2. En jQuery $ () es una llamada a la función. C # no tiene funciones globales, una llamada a la función estática es el más cercano. Me gustaría utilizar un método que indica que usted está creando un envoltorio como ..
SharpQuery.Create("ul.first").Find(".foo")
Yo no estaría preocupado por el acortamiento de SharpQuery cuadrados ya que los usuarios medios de IntelliSense no tendrán que escribir todo el asunto (y si tienen ReSharper sólo tienen que escribir todos modos SQ).