2010-11-04 17 views
5

Tôi là making a jquery clone cho C#. Ngay bây giờ tôi đã thiết lập nó sao cho mọi phương thức là một phương thức mở rộng trên IEnumerable<HtmlNode> để nó hoạt động tốt với các dự án hiện có đang sử dụng HtmlAgilityPack. Tôi nghĩ rằng tôi có thể nhận được đi mà không cần bảo quản nhà nước ... tuy nhiên, sau đó tôi nhận thấy jQuery có hai phương pháp .andSelf.end mà "pop" các yếu tố phù hợp gần đây nhất ra khỏi một ngăn xếp nội bộ. Tôi có thể bắt chước chức năng này nếu tôi thay đổi lớp của mình để nó luôn hoạt động trên các đối tượng SharpQuery thay vì đếm, nhưng vẫn còn một vấn đề.Làm thế nào để thiết kế C# jQuery API của tôi như vậy mà nó không phải là khó hiểu để sử dụng?

Với JavaScript, bạn được cung cấp tài liệu Html tự động, nhưng khi làm việc trong C# bạn phải tải nó một cách rõ ràng và bạn có thể sử dụng nhiều tài liệu nếu muốn. Có vẻ như khi bạn gọi $('xxx'), về cơ bản bạn đang tạo một đối tượng jQuery mới và bắt đầu mới với một chồng trống. Trong C#, bạn sẽ không muốn làm điều đó, bởi vì bạn không muốn tải lại/nạp lại tài liệu từ web. Vì vậy, thay vào đó, bạn tải nó một lần hoặc vào một đối tượng SharpQuery, hoặc vào một danh sách các HtmlNodes (bạn chỉ cần DocumentNode để bắt đầu).

Trong các tài liệu jQuery, họ đưa ra ví dụ này

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

Tôi không có một phương pháp khởi tạo bởi vì tôi không thể nạp chồng toán tử (), vì vậy bạn chỉ cần bắt đầu với sq.Find() thay vào đó, mà hoạt động trên gốc của tài liệu, về cơ bản làm điều tương tự. Nhưng sau đó mọi người sẽ thử và viết sq.Find() trên một dòng, và sau đó sq.Find() ở đâu đó trên đường và (đúng) mong đợi nó hoạt động trên thư mục gốc của tài liệu một lần nữa ... nhưng nếu tôi duy trì trạng thái thì bạn vừa sửa đổi ngữ cảnh sau cuộc gọi đầu tiên.

Vậy ... tôi nên thiết kế API của mình như thế nào? Tôi có thêm phương thức Init khác là tất cả các truy vấn nên bắt đầu bằng cách đặt lại ngăn xếp (nhưng sau đó làm cách nào để bắt buộc chúng bắt đầu bằng cách đó?) Hoặc thêm Reset() mà chúng phải gọi ở cuối dòng? Tôi có quá tải các [] thay vào đó và nói với họ để bắt đầu với điều đó? Tôi có nói "quên nó đi, không ai sử dụng những chức năng được nhà nước bảo tồn đó không?"

Về cơ bản, bạn muốn viết ví dụ jQuery như thế nào trong C#?

  1. sq["ul.first"].Find(".foo") ...
    downfalls: Lạm dụng các [] tài sản.

  2. sq.Init("ul.first").Find(".foo") ...
    downfalls: Không có gì thực sự buộc các lập trình viên để bắt đầu với Init, trừ khi tôi thêm một số cơ chế "khởi tạo" lạ; người dùng có thể thử bắt đầu với .Find và không nhận được kết quả mong đợi. Ngoài ra, InitFind là khá nhiều giống hệt nhau, ngoại trừ trước đây đặt lại ngăn xếp quá.

  3. sq.Find("ul.first").Find(".foo") ... .ClearStack()
    downfalls: lập trình viên có thể quên để xóa các ngăn xếp.

  4. Không thể làm điều đó.
    end() không được triển khai.

  5. Sử dụng hai đối tượng khác nhau.
    Có thể sử dụng HtmlDocument làm cơ sở mà tất cả truy vấn sẽ bắt đầu bằng, và sau đó mọi phương thức sau đó trả về đối tượng SharpQuery có thể bị xích. Bằng cách đó, HtmlDocument luôn duy trì trạng thái ban đầu, nhưng đối tượng SharpQuery có thể có các trạng thái khác nhau. Điều này không may có nghĩa là tôi phải thực hiện một loạt các công cụ hai lần (một lần cho HtmlDocument, một lần cho đối tượng SharpQuery).

  6. new SharpQuery(sq).Find("ul.first").Find(".foo") ...
    Bản sao constructor một tham chiếu đến các tài liệu, nhưng reset stack.

+2

IMO 5) là tùy chọn tốt nhất. Nó chắc chắn có thể đọc được và trực quan cho các truy vấn bắt đầu từ 'HtmlDocument', và đối với tác phẩm trùng lặp - hy vọng bạn có thể thực hiện nó sao cho chỉ các chữ ký phương thức được sao chép, chứ không phải logic thực tế. (Chaining các cuộc gọi đến một số lớp học phổ biến mà không công việc.) Tôi cũng thích tùy chọn 1) và cá nhân không nhìn thấy nó như là một "lạm dụng" của các dấu ngoặc đơn. :) –

+0

@Kirk: '[]' thường chỉ ra thao tác 'O (1)' ... giống như dữ liệu đã được lập chỉ mục. Trong trường hợp này, nó thực sự phải thực hiện tìm kiếm đầy đủ. – mpen

+0

Tuy nhiên, tôi đang hướng tới (1) ngay bây giờ. Trên thực tế, tôi đã bắt đầu viết mã theo cách này. Điều này ngăn người dùng nhảy ngay với '.Find()' vì bối cảnh không được đặt cho đến khi họ gọi '[]'. tức là '.Find()' đơn giản sẽ không tìm thấy bất kỳ thứ gì vì nó không có gì để tìm kiếm. '[]' đặt lại ngữ cảnh cho nút tài liệu và sau đó chúng có thể bắt đầu tìm kiếm. – mpen

Trả lời

4

Tôi nghĩ rằng trở ngại chính mà bạn đang gặp phải ở đây là bạn đang cố gắng tránh xa chỉ với một đối tượng SharpQuery cho mỗi tài liệu. Đó không phải là cách jQuery hoạt động; nói chung, các đối tượng jQuery là không thay đổi. Khi bạn gọi một phương thức thay đổi các thiết lập của các yếu tố (như find hoặc end hoặc add), nó không làm thay đổi các đối tượng hiện có, nhưng trả về một hình mới:

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

(thấy documentation of end để biết thêm)

SharpQuery sẽ hoạt động theo cùng một cách. Khi bạn tạo một đối tượng SharpQuery với một tài liệu, các cuộc gọi phương thức sẽ trả về các đối tượng mới SharpQuery, tham chiếu một tập hợp các phần tử khác nhau của cùng một tài liệu. Ví dụ:

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 

Lợi ích của phương pháp này là một vài. Bởi vì sq, header, allTheLinks, v.v ... đều là cùng một lớp, bạn chỉ có một triển khai cho từng phương pháp. Tuy nhiên, mỗi đối tượng trong số này tham chiếu cùng một tài liệu, do đó bạn không có nhiều bản sao của mỗi nút và các thay đổi đối với các nút được phản ánh trong mọi đối tượng SharpQuery trên tài liệu đó (ví dụ: sau allTheLinks.text("foo"), someOfTheLinks.text() == "foo".).

Triển khai end và các thao tác dựa trên ngăn xếp khác cũng trở nên dễ dàng. Vì mỗi phương thức tạo đối tượng SharpQuery mới, được lọc từ một đối tượng khác, nó giữ lại tham chiếu đến đối tượng gốc đó (allTheLinks tới header, header đến sq). Sau đó end cũng đơn giản như trả lại một mới SharpQuery chứa các yếu tố tương tự như cha mẹ, như:

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

(hoặc tuy nhiên cú pháp của bạn lắc ra.)

Tôi nghĩ rằng phương pháp này sẽ giúp bạn có được jQuery nhất giống như hành vi, với việc thực hiện khá dễ dàng.Tôi chắc chắn sẽ theo dõi dự án này; đó là một ý tưởng tuyệt vời.

+0

Ahh..clever! Tôi đã nghĩ về việc trả lại một vật thể mới, nhưng sau đó tôi không thể tìm ra cách tôi sẽ duy trì ngăn xếp. Giữ một tham chiếu đến phụ huynh ... rất đơn giản>. < – mpen

+0

Cập nhật lớn: http://sharp-query.googlecode.com/svn/trunk/HtmlAgilityPlus/ Sử dụng phương pháp này để chuỗi bây giờ. – mpen

0

Tôi sẽ nghiêng về một biến thể trên tùy chọn 2. Trong jQuery $() là một cuộc gọi hàm. C# không có chức năng toàn cầu, một cuộc gọi hàm tĩnh là gần nhất. Tôi sẽ sử dụng một phương pháp mà cho thấy bạn đang tạo một wrapper như ..

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

tôi sẽ không quan tâm đến rút ngắn SharpQuery để SQ từ IntelliSense nghĩa là người dùng sẽ không cần phải gõ toàn bộ điều (và nếu họ đã resharper họ chỉ cần gõ SQ anyways).

+0

Họ có thể đặt tên cho nó bất cứ điều gì họ muốn .. 'sq' sẽ là một ví dụ của' SharpQuery'. 'SharpQuery.Create' sẽ không hoạt động như một phương thức tĩnh vì bạn phải nạp tài liệu đầu tiên ... trừ khi bạn muốn làm cho tài liệu tĩnh quá, nhưng sau đó bạn chỉ có thể làm việc với một tài liệu cùng một lúc; một hạn chế tùy ý. – mpen

Các vấn đề liên quan