mpe*_*pen 5 c# interface-design jquery html-agility-pack
我正在为C#制作一个jquery克隆.现在我已经设置好了,所以每个方法都是一个扩展方法,IEnumerable<HtmlNode>所以它适用于已经使用的现有项目HtmlAgilityPack.我以为我可以在不保留状态的情况下离开...然而,我注意到jQuery有两种方法.andSelf,.end它们会从内部堆栈"弹出"最近匹配的元素.如果我改变我的类以便它总是在SharpQuery对象上运行而不是枚举,我可以模仿这个功能,但是仍然存在问题.
使用JavaScript,您可以自动获得Html文档,但在使用C#时,您必须明确加载它,如果需要,您可以使用多个文档.看来,当你打电话给$('xxx')你时,你实际上是在创建一个新的jQuery对象,并以空堆栈开始.在C#中,您不希望这样做,因为您不想从Web重新加载/重新获取文档.因此,您只需将其加载到SharpQuery对象中,或加载到HtmlNodes列表中(您只需要开始使用DocumentNode).
在jQuery文档中,他们给出了这个例子
$('ul.first').find('.foo')
.css('background-color', 'red')
.end().find('.bar')
.css('background-color', 'green')
.end();
Run Code Online (Sandbox Code Playgroud)
我没有初始化方法因为我不能重载()运算符,所以你只需要开始sq.Find(),它在文档的根目录上操作,基本上做同样的事情.但是后来人们会尝试sq.Find()在一条线上写字,然后在sq.Find()某个地方写下来,并且(理所当然地)期望它再次在文档的根部运行......但是如果我保持状态,那么你就是刚刚在第一次调用后修改了上下文.
那么......我应该如何设计我的API?我是否添加了另一个Init方法,所有查询应该从重置堆栈开始(但是我如何强制它们从那开始呢?),或者添加一个Reset()他们必须在他们的行尾调用的方法?我是否超载[]而告诉他们从那开始?我会说"忘记它,没有人使用那些保存状态的功能吗?"
基本上,您希望如何用C#编写jQuery示例?
sq["ul.first"].Find(".foo") ...
垮台:虐待[]财产.
sq.Init("ul.first").Find(".foo") ...
失败:除非我添加一些奇怪的"初始化"机制,否则没有什么能真正迫使程序员从Init开始; 用户可能会尝试使用.Find而不是获得他期望的结果.此外,Init和Find是几乎相同的,无论如何,除了前者把堆栈复位了.
sq.Find("ul.first").Find(".foo") ... .ClearStack()
垮台:程序员可能忘记清理堆栈.
不能这样做.
end()未实现.
使用两个不同的对象.
也许HtmlDocument用作所有查询应该开始的基础,然后每个方法返回一个SharpQuery可以链接的对象.这样,HtmlDocument始终保持初始状态,但SharpQuery对象可能具有不同的状态.遗憾的是,我必须实现两次(一次用于HtmlDocument,一次用于SharpQuery对象).
new SharpQuery(sq).Find("ul.first").Find(".foo") ...
构造函数复制对文档的引用,但重置堆栈.
我认为您在这里遇到的主要障碍是您试图摆脱SharpQuery每个文档只有一个对象的问题。jQuery 不是这样工作的;一般来说,jQuery 对象是不可变的。当您调用更改元素集的方法(例如findorend或add)时,它不会更改现有对象,而是返回一个新对象:
var theBody = $('body');
// $('body')[0] is the <body>
theBody.find('div').text('This is a div');
// $('body')[0] is still the <body>
Run Code Online (Sandbox Code Playgroud)
(有关更多信息,请参阅的文档)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
Run Code Online (Sandbox Code Playgroud)
这种方法的好处有很多。因为sq、header、allTheLinks等都是同一个类,所以每个方法只有一个实现。然而,这些对象中的每一个都引用相同的文档,因此您没有每个节点的多个副本,并且对节点的更改会反映在SharpQuery该文档上的每个对象中(例如allTheLinks.text("foo"),在someOfTheLinks.text() == "foo", . 之后)。
实现end和其他基于堆栈的操作也变得很容易。当每个方法从另一个方法创建一个新的、过滤的SharpQuery对象时,它保留对该父对象的引用(allTheLinksto header、headerto sq)。然后end就像返回一个SharpQuery包含与父元素相同的元素的新元素一样简单,例如:
public SharpQuery end()
{
return new SharpQuery(this.parent.GetAllElements());
}
Run Code Online (Sandbox Code Playgroud)
(或者不管你的语法如何变化。)
我认为这种方法将给你带来最像 jQuery 的行为,并且实现相当简单。我一定会关注这个项目;这是个好主意。