2012-07-09 2 views
17

Đồng nghiệp của tôi và tôi có tranh chấp. Chúng tôi đang viết một ứng dụng .NET xử lý một lượng lớn dữ liệu. Nó nhận các phần tử dữ liệu, nhóm các tập hợp con của chúng thành các khối theo một số tiêu chí và xử lý các khối đó.Tôi có nên để IObservable <T> trên giao diện của mình không?

Giả sử chúng tôi có các mục dữ liệu thuộc loại Foo đến một số nguồn (từ mạng, chẳng hạn) từng cái một. Chúng tôi muốn thu thập tập hợp con của các đối tượng liên quan thuộc loại Foo, xây dựng một đối tượng thuộc loại Bar từ mỗi tập hợp con này và xử lý các đối tượng thuộc loại Bar.

Một trong số chúng tôi đã đề xuất thiết kế sau. Chủ đề chính của nó là phơi bày các đối tượng trực tiếp từ các giao diện của các thành phần của chúng tôi IObservable<T>.

Thiết kế khác được đề xuất khác rằng chủ đề chính của nó là sử dụng giao diện nhà xuất bản/thuê bao của riêng chúng tôi và chỉ sử dụng Rx bên trong triển khai khi cần.

//********** interfaces ********* 

interface IPublisher<T> 
{ 
    void Subscribe(ISubscriber<T> subscriber); 
} 

interface ISubscriber<T> 
{ 
    Action<T> Callback { get; } 
} 


//********** implementations ********* 

class FooSource : IPublisher<Foo> 
{ 
    public void Subscribe(ISubscriber<Foo> subscriber) { /* ... */ } 

    // here we put logic that receives Foo objects from some source (the network?) publishes them to the registered subscribers 
} 

class FooSubsetsToBarConverter : ISubscriber<Foo>, IPublisher<Bar> 
{ 
    void Callback(Foo foo) 
    { 
     // here we put logic that aggregates Foo objects and publishes Bars when we have received a subset of Foos that match our criteria 
     // maybe we use Rx here internally. 
    } 

    public void Subscribe(ISubscriber<Bar> subscriber) { /* ... */ } 
} 

class BarsProcessor : ISubscriber<Bar> 
{ 
    void Callback(Bar bar) 
    { 
     // here we put code that processes Bar objects 
    } 
} 

//********** program ********* 
class Program 
{ 
    public static void Main(string[] args) 
    { 
     var fooSource = fooSourceFactory.Create(); 
     var barsProcessor = barsProcessorFactory.Create(fooSource) // this will create BarsProcessor and perform all the necessary subscriptions 

     fooSource.Run(); // this enters a loop of listening for Foo objects from the network and notifying about their arrival. 
    } 
} 

Bạn nghĩ điều gì tốt hơn? Phơi bày IObservable<T> và làm cho các thành phần của chúng tôi tạo luồng sự kiện mới từ các nhà khai thác Rx hoặc xác định giao diện nhà xuất bản/thuê bao của riêng chúng tôi và sử dụng Rx nội bộ nếu cần?

Dưới đây là một số điều cần lưu ý về thiết kế:

  • Trong thiết kế đầu tiên cho người tiêu dùng các giao diện của chúng tôi có toàn bộ sức mạnh của Rx ở/đầu ngón tay của mình và có thể thực hiện bất kỳ nhà khai thác Rx. Một trong số chúng tôi tuyên bố đây là một lợi thế và những người khác cho rằng đây là một nhược điểm.

  • Thiết kế thứ hai cho phép chúng tôi sử dụng bất kỳ kiến ​​trúc nhà xuất bản/người đăng ký nào dưới mui xe. Thiết kế đầu tiên liên kết chúng tôi với Rx.

  • Nếu muốn sử dụng sức mạnh của Rx, nó đòi hỏi nhiều công việc hơn trong thiết kế thứ hai bởi vì chúng tôi cần dịch triển khai nhà xuất bản/người đăng ký tùy chỉnh thành Rx và ngược lại. Nó đòi hỏi phải viết mã keo cho mỗi lớp muốn thực hiện một số xử lý sự kiện.

+3

Tôi thích cách bạn bắt đầu câu hỏi này. "Tôi và đồng nghiệp của tôi có tranh chấp." +1. –

+3

Tại sao không phơi bày tất cả các công cụ IObservable như * phương pháp mở rộng * mà chăm sóc của tất cả những "mã keo".Giữ tất cả các công cụ IObs tách biệt khỏi các mô hình đối tượng của bạn, trong khi cung cấp tùy chọn. 'public IObservable AsObservable (IPublisher publisher)' hoặc một cái gì đó tương tự – Will

+4

Bạn đã làm tốt công việc hỏi câu hỏi một cách cân bằng. –

Trả lời

14

Phơi bày IObservable<T> không gây ô nhiễm thiết kế với Rx theo bất kỳ cách nào. Trong thực tế, quyết định thiết kế là chính xác giống như đang chờ xử lý giữa việc trưng ra một sự kiện .NET cũ của trường học hoặc chuyển cơ chế pub/sub của riêng bạn. Điểm khác biệt duy nhất là IObservable<T> là khái niệm mới hơn.

Cần bằng chứng? Nhìn vào F # cũng là một ngôn ngữ .NET nhưng nhỏ hơn C#. Trong F #, mọi sự kiện đều xuất phát từ IObservable<T>. Thành thật mà nói, tôi thấy không có ý nghĩa trong việc trừu tượng hóa một cơ chế pub/sub .NET phù hợp hoàn hảo - đó là IObservable<T> - đi với pub pub/sub abstract của bạn. Chỉ cần phơi bày IObservable<T>.

Làm cho quán rượu/tiểu trừu tượng của riêng bạn cảm thấy giống như áp dụng các mẫu Java cho mã .NET cho tôi. Sự khác biệt là, trong .NET luôn có sự hỗ trợ khung công tác tuyệt vời cho mẫu Observer và đơn giản là không cần phải cuộn của riêng bạn.

0

Một lựa chọn khác có thể là:

interface IObservableFooSource : IFooSource 
{ 
    IObservable<Foo> FooArrivals 
    { 
     get; 
    } 
} 

class FooSource : IObservableFooSource 
{ 
    // Implement the interface explicitly 
    IObservable<Foo> IObservableFooSource.FooArrivals 
    { 
     get 
     { 
     } 
    } 
} 

Bằng cách này chỉ khách hàng mà mong đợi một IObservableFooSource sẽ thấy những phương pháp RX-cụ thể, những người mong đợi một IFooSource hoặc một FooSource sẽ không.

8

Trước hết, đó là đáng chú ý là IObservable<T> là một phần của mscorlib.dllSystem namespace, và do đó phơi bày nó sẽ là hơi tương đương với lộ IComparable<T> hoặc IDisposable. Đó là tương đương với chọn .NET làm nền tảng của bạn, mà bạn dường như đã làm.

Bây giờ, thay vì đề xuất câu trả lời, tôi muốn đề xuất một câu hỏi khác, và sau đó là một suy nghĩ khác, và tôi hy vọng (và tin tưởng) mà bạn sẽ quản lý từ đó.

Về cơ bản, bạn đang yêu cầu: Chúng tôi có muốn quảng bá việc sử dụng rải rác các toán tử Rx trên toàn hệ thống của chúng tôi không?.Bây giờ rõ ràng đó không phải là rất hấp dẫn, nhìn thấy như bạn có thể xử lý Rx khái niệm như là một thư viện bên thứ 3.

Dù bằng cách nào, câu trả lời không nằm trong các thiết kế cơ bản mà bạn đã đề xuất, nhưng trong người dùng thiết kế đó. Tôi khuyên bạn nên phá vỡ thiết kế của bạn xuống đến mức trừu tượng, và đảm bảo rằng việc sử dụng các toán tử Rx được scoped chỉ trong một mức. Khi tôi nói về mức độ trừu tượng, tôi có nghĩa là một cái gì đó tương tự như OSI Model, chỉ trong cùng một mã của ứng dụng.

Điều quan trọng nhất, trong cuốn sách của tôi, không phải là quan điểm thiết kế của "Hãy tạo thứ gì đó sẽ được sử dụng và phân tán trên toàn hệ thống, và vì vậy chúng tôi cần đảm bảo chúng tôi làm điều đó một lần và vừa phải, trong tất cả các năm tới ". Tôi là một trong số "Hãy tạo lớp trừu tượng này tạo API tối thiểu cần thiết cho các lớp khác để hiện đang đạt được mục tiêu".

Về sự đơn giản của cả hai thiết kế của bạn, nó thực sự khó để phán xét từ FooBar không cho tôi biết nhiều về trường hợp sử dụng, và do đó các yếu tố dễ đọc (mà là, bằng cách này, khác nhau từ một trong những sử dụng trường hợp khác).

2

Trong thiết kế đầu tiên, người tiêu dùng giao diện của chúng tôi có toàn bộ sức mạnh của Rx ở đầu ngón tay và có thể thực hiện bất kỳ toán tử Rx nào. Một trong số chúng tôi tuyên bố đây là một lợi thế và những người khác cho rằng đây là một nhược điểm.

Tôi đồng ý với tính khả dụng của Rx làm lợi thế. Liệt kê một số lý do tại sao nó là một nhược điểm có thể giúp xác định cách giải quyết chúng. Một số ưu điểm tôi thấy là:

  • Như Yam và Christoph cả chải chống lại, IObservable/IObserver là trong mscorlib như của .NET 4.0, vì vậy nó sẽ (hy vọng) trở thành một khái niệm tiêu chuẩn mà tất cả mọi người ngay lập tức sẽ hiểu, như các sự kiện hoặc IEnumerable.
  • Các toán tử của Rx. Sau khi bạn cần soạn, lọc hoặc xử lý nhiều luồng có khả năng, chúng sẽ trở nên rất hữu ích. Có thể bạn sẽ thấy mình làm lại công việc này dưới một số dạng với giao diện của riêng bạn.
  • Hợp đồng của Rx. Thư viện Rx thực thi một hợp đồng được xác định rõ ràng và thực hiện càng nhiều việc thực thi hợp đồng đó càng tốt. Ngay cả khi bạn cần phải thực hiện các toán tử của riêng bạn, Observable.Create sẽ thực hiện công việc để thực thi hợp đồng (đó là lý do tại sao thực hiện trực tiếp IObservable không được nhóm Rx khuyến nghị).
  • Thư viện Rx có các cách tốt để đảm bảo bạn kết thúc đúng chuỗi khi cần.

Tôi đã viết chia sẻ của các nhà khai thác nơi thư viện không bao gồm trường hợp của tôi.

Thiết kế thứ hai cho phép chúng tôi sử dụng bất kỳ kiến ​​trúc nhà xuất bản/người đăng ký nào dưới mui xe. Thiết kế đầu tiên liên kết chúng tôi với Rx.

Tôi không thấy như thế nào lựa chọn để lộ Rx có nhiều, nếu có, ảnh hưởng đến cách bạn thực hiện các kiến ​​trúc dưới mui xe nữa vì sử dụng giao diện riêng của bạn sẽ. Tôi sẽ khẳng định rằng bạn không nên phát minh ra các kiến ​​trúc pub/sub mới trừ khi thực sự cần thiết.

Hơn nữa, thư viện Rx có thể có các toán tử sẽ đơn giản hóa các phần "dưới mui xe".

Nếu chúng ta muốn sử dụng sức mạnh của Rx, nó đòi hỏi nhiều công việc trong thiết kế thứ hai vì chúng tôi cần dịch nhà xuất bản tùy chỉnh/triển khai người đăng ký sang Rx và ngược lại. Nó đòi hỏi phải viết mã keo cho mỗi lớp muốn thực hiện một số xử lý sự kiện.

Có và không. Điều đầu tiên tôi nghĩ nếu tôi thấy thiết kế thứ hai là: "Điều đó gần giống như IObservable; hãy viết một số phương thức mở rộng để chuyển đổi các giao diện." Mã keo được viết một lần, được sử dụng ở mọi nơi.

Mã keo là đơn giản, nhưng nếu bạn nghĩ rằng bạn sẽ sử dụng Rx, chỉ cần phơi bày IObservable và tiết kiệm cho mình những rắc rối.

cân nhắc Hơn nữa

Về cơ bản, thiết kế thay thế khác trong 3 cách chính từ IObservable/IObserver.

  1. Không có cách nào để hủy đăng ký. Điều này có thể chỉ là một sự giám sát khi sao chép vào câu hỏi. Nếu không, đó là một cái gì đó để xem xét mạnh mẽ thêm nếu bạn đi con đường đó.
  2. Không có đường dẫn được xác định cho các lỗi chảy xuống hạ lưu (ví dụ: IObserver.OnError).
  3. Không có cách nào để cho biết việc hoàn thành một luồng (ví dụ: IObserver.OnCompleted). Điều này chỉ phù hợp nếu dữ liệu cơ bản của bạn có ý định có điểm kết thúc.

Thiết kế thay thế của bạn cũng trả về gọi lại dưới dạng hành động thay vì đặt nó làm phương thức trên giao diện nhưng tôi không nghĩ sự khác biệt là quan trọng.

Thư viện Rx khuyến khích phương pháp tiếp cận chức năng. Lớp học FooSubsetsToBarConverter của bạn sẽ phù hợp hơn với phương pháp mở rộng là IObservable<Foo> trả về IObservable<Bar>. Điều này làm giảm sự lộn xộn một chút (tại sao tạo một lớp với một thuộc tính khi một hàm sẽ làm tốt) và phù hợp hơn với thành phần kiểu chuỗi của phần còn lại của thư viện Rx. Bạn có thể áp dụng cùng một cách tiếp cận với các giao diện thay thế, nhưng không có các toán tử để trợ giúp, nó có thể khó khăn hơn.

+1

+1 Câu trả lời hay; hợp đồng là rất quan trọng. Có một số đảm bảo rằng chúng tôi nhận được miễn phí nếu chúng tôi ở bên trong Rx, bao gồm cả bảo đảm tác dụng phụ miễn phí với các đơn vị LINQ khác như IEnumerable/IQueryable/IQbservable. Nhà quán rượu/phụ họ không thể có những đảm bảo trong thiết kế của họ, trừ khi họ về cơ bản tái tạo LINQ/Rx (monads). –