2009-09-25 29 views
26

Tôi đã có GOF ngồi trên bàn của tôi ở đây và tôi biết phải có một số mẫu thiết kế giải quyết được vấn đề tôi đang gặp, nhưng tôi không thể hình dung ra .Mẫu thiết kế để xử lý nhiều loại tin nhắn

Vì mục đích đơn giản, tôi đã thay đổi tên của một số giao diện mà tôi đang sử dụng.

Vì vậy, đây là vấn đề, ở một bên của dây, tôi có nhiều máy chủ gửi các loại tin nhắn khác nhau. Ở phía bên kia của dây tôi có một khách hàng cần để có thể xử lý tất cả các loại tin nhắn khác nhau.

Tất cả thư thực hiện cùng giao diện phổ biến IMessage. Vấn đề của tôi là, khi khách hàng nhận được một IMessage mới, làm thế nào để nó biết loại IMessage của nó nhận được?

Tôi cho rằng mình có thể làm điều gì đó như sau, nhưng điều này chỉ FEELS khủng khiếp.

TradeMessage tMessage = newMessage as TradeMessage; 
if (tMessage != null) 
{ 
    ProcessTradeMessage(tMessage); 
} 

OrderMessage oMessage = newMessage as OrderMessage; 
if (oMessage != null) 
{ 
    ProcessOrderMessage(oMessage); 
} 

Ý nghĩ thứ hai là thêm một thuộc tính vào IMessage gọi là MessageTypeID, nhưng điều đó sẽ yêu cầu tôi viết một cái gì đó như sau, cũng FEELS khủng khiếp.

TradeMessage tMessage = new TradeMessage(); 
if (newMessage.MessageTypeID == tMessage.MessageTypeID) 
{ 
    tMessage = newMessage as TradeMessage; 
    ProcessTradeMessage(tMessage); 
} 

OrderMessage oMessage = new OrderMessage(); 
if (newMessage.MessageTypeID == oMessage.MessageTypeID) 
{ 
    oMessage = newMessage as OrderMessage; 
    ProcessOrderMessage(oMessage); 
} 

Tôi biết vấn đề chung này đã được giải quyết một triệu lần, vì vậy phải có một cách đẹp hơn giải quyết vấn đề của việc có một phương pháp mà có một giao diện như một tham số, nhưng cần kiểm soát dòng chảy khác nhau dựa trên lớp nào đã triển khai giao diện đó.

+0

tôi đã thêm một trả lời tương tự như một trong những bạn chấp nhận rằng hoạt động tắt của gợi ý @ Grianon của mà tôi nghĩ rằng bạn sẽ tìm thấy các công trình tốt bằng cách sử dụng công văn đôi. – SwDevMan81

Trả lời

17

Bạn có thể tạo xử lý tin nhắn riêng cho từng loại tin nhắn, và ngây thơ vượt qua thông điệp cho mỗi điều khiển có sẵn cho đến khi bạn tìm thấy một mà có thể xử lý nó. Tương tự như chuỗi trách nhiệm mẫu:

public interface IMessageHandler { 
    bool HandleMessage(IMessage msg); 
} 

public class OrderMessageHandler { 
    bool HandleMessage(IMessage msg) { 
     if (!msg is OrderMessage) return false; 

     // Handle the message. 
    } 
} 

public class SomeOtherMessageHandler { 
    bool HandleMessage(IMessage msg) { 
     if (!msg is SomeOtherMessage) return false; 

     // Handle the message. 
    } 
} 

... etc ... 

public class MessageProcessor { 
    private List<IMessageHandler> handlers; 

    public MessageProcessor() { 
     handlers = new List<IMessageHandler>(); 
     handlers.add(new SomeOtherMessageHandler()); 
     handlers.add(new OrderMessageHandler()); 
    } 

    public void ProcessMessage(IMessage msg) { 
     bool messageWasHandled 
     foreach(IMessageHandler handler in handlers) { 
      if (handler.HandleMessage(msg)) { 
       messageWasHandled = true; 
       break; 
      } 
     } 

     if (!messageWasHandled) { 
      // Do some default processing, throw error, whatever. 
     } 
    } 
} 

Bạn cũng có thể triển khai bản đồ này, với tên lớp thông báo hoặc id loại tin nhắn làm khóa và phiên bản trình xử lý thích hợp làm giá trị.

Những người khác đã đề xuất có đối tượng tin nhắn "xử lý" chính nó, nhưng điều đó không cảm thấy phù hợp với tôi. Có vẻ như cách tốt nhất là tách riêng việc xử lý thư khỏi bản thân thư.

Một số điều khác tôi thích về nó:

  1. Bạn có thể tiêm các trình xử lý tin nhắn qua mùa xuân hoặc những gì-có-bạn chứ không phải là tạo ra chúng trong các nhà xây dựng, làm cho này rất thể kiểm chứng.

  2. Bạn có thể giới thiệu hành vi giống như chủ đề, nơi bạn có nhiều trình xử lý cho một thư đơn giản bằng cách xóa "ngắt" khỏi vòng lặp ProcessMessage.

  3. Bằng cách tách riêng các thông điệp từ bộ xử lý, bạn có thể có bộ xử lý khác nhau đối với cùng một thông điệp tại điểm đến khác nhau (ví dụ nhiều lớp MessageProcessor có thể xử lý các thông điệp tương tự khác)

+0

+1 Tôi nghĩ đây là một ý tưởng hay. –

+1

Tất cả các giải pháp đều tuyệt vời, nhưng điều này phù hợp với nhu cầu của ứng dụng này tốt nhất. một TradeMessage sẽ không được xử lý giống nhau bởi tất cả các ứng dụng, do đó, nó không có ý nghĩa để đưa logic xử lý vào đối tượng TradeMessage. –

+1

'if (! Msg là SomeOtherMessage)' nên là 'if (! (Msg là SomeOtherMessage))'. 'OrderMessageHandler' và' SomeOtherMessageHandler' sẽ triển khai 'IMessageHandler' để có thể thêm chúng vào trình xử lý' List . Ngoài ra 'bool messageWasHandled' bỏ lỡ một'; '. (Bạn đã thực sự kiểm tra điều này?) –

14

Một vài giải pháp có thể áp dụng cho điều này, trước tiên là giải pháp tốt nhất, cuối cùng là tốt nhất. Tất cả các ví dụ là giả:

1st, và giải pháp tốt nhất

Vincent Ramdhanie giới thiệu mô hình đúng thực tế để giải quyết vấn đề này, được gọi là strategy pattern.

Mẫu này tạo ra một 'bộ xử lý' riêng biệt, trong trường hợp này là xử lý các thông báo cho phù hợp.

Nhưng tôi khá chắc chắn một lời giải thích tốt được đưa ra trong cuốn sách của bạn bằng các GOF :)

Như nhận xét, thông điệp không thể để xử lý riêng của mình, nó vẫn là hữu ích để tạo ra một giao diện cho tin nhắn, hoặc một lớp cơ sở, vì vậy bạn có thể thực hiện một chức năng xử lý chung cho một tin nhắn, và quá tải nó cho những cái cụ thể hơn.

quá tải là trong bất kỳ trường hợp tốt hơn sau đó tạo ra một phương pháp khác nhau cho tất cả các loại tin nhắn ...

public class Message {} 
public class TradeMessage extends Message {} 

public class MessageProcessor { 
    public function process(Message msg) { 
     //logic 
    } 

    public function process(TradeMessage msg) { 
     //logic 
    } 
} 

3rd

Nếu thông điệp của bạn có thể xử lý bản thân bạn có thể viết một giao diện, kể từ phương thức xử lý của bạn phụ thuộc vào thông điệp bạn nhận được, có vẻ như dễ dàng hơn để đưa nó vào trong lớp thông báo ...

public interface IMessage 
{ 
    public function process(){} 
} 

sau đó bạn thực hiện điều này trong tất cả các lớp thông điệp của bạn và proccess họ:

list = List<IMessage>(); 
foreach (IMessage message in list) { 
    message.process(); 
} 

trong danh sách của bạn, bạn có thể lưu trữ bất kỳ lớp mà thực hiện giao diện mà ...

+1

Tôi giả định rằng hàm ProcessXMessage() của anh ta được đặt trên mã bên ngoài đối tượng của anh ta, vì vậy đối tượng của anh ta không thể tự xử lý nội bộ. – JustLoren

+0

Khi tôi hiểu được sự cố, thư có thể không có bất kỳ hành vi nào liên quan đến nó. Vì vậy, tôi nghĩ rằng một bộ xử lý tin nhắn riêng biệt với hành vi * quá tải * sẽ là giải pháp tốt nhất. –

+0

@bruno conde: Giống như bài đăng của Vincent Ramdhanie - tôi đồng ý, đây có lẽ là triển khai tổng thể tốt nhất. – JustLoren

1

Thêm một ProcessMessage () phương pháp cho giao diện iMessage và để cho thông điệp cụ thể đa hình quyết định đúng cách để tự xử lý.

Mã của bạn sau đó trở thành

newMessage.ProcessMessage(); 

Đây là một bài viết tốt về using polymorphism instead of conditionals.

1

Trong trường hợp tương tự, tôi có một máy chủ nhận nhiều thông điệp khác nhau từ nhiều khách hàng.

Tất cả thư được gửi theo thứ tự và bắt đầu bằng số nhận dạng của loại thư. Sau đó tôi có một tuyên bố chuyển đổi nhìn vào định danh. Các thông điệp sau đó được deserialized (đối tượng rất khác nhau) và xử lý khi thích hợp.

Điều tương tự có thể được thực hiện bằng cách truyền các đối tượng triển khai giao diện bao gồm cách chỉ ra loại thông báo.

public void ProcessMessage(IMessage msg) 
{ 
    switch(msg.GetMsgType()) // GetMsgType() is defined in IMessage 
    { 
     case MessageTypes.Order: 
      ProcessOrder(msg as OrderMessage); // Or some other processing of order message 
      break; 
     case MessageTypes.Trade: 
      ProcessTrade(msg as TradeMessage); // Or some other processing of trade message 
     break; 
     ... 
    } 
} 
+2

Tôi đã thay thế việc sử dụng này bằng từ điển các lệnh thực hiện giao diện chung và được lập chỉ mục theo loại. Ít mã hơn. Chống lại các biểu tượng chuyển đổi lớn với mẫu lệnh này –

+0

Xin chào, Làm thế nào để bạn thực hiện tuần tự hóa? Bạn có sắp xếp từng thông điệp một cách riêng biệt không? Bạn có thể xin ví dụ không? – ransh

4

Một tùy chọn là có thư đi kèm với trình xử lý của riêng chúng. Tức là, tạo một Giao diện gọi là IMessageProcessor, chỉ định phương thức processMessage (IMessage). Tiếp theo xác định lớp cụ thể thực hiện IMessageProcessor cho từng loại thông báo.

Mỗi lớp IMessage sau đó sẽ xác định Bộ xử lý của riêng nó.

Khi bạn receieve một đối tượng thông điệp mà bạn sẽ làm điều gì đó như thế này:

message.processor.processMessage(); 
2

Bạn có thể muốn tham gia một xem qua số Enterprise Integration Patterns của Gregor Hohpe và Bobby Woolf. Nó có một danh mục tốt các mẫu để xử lý tin nhắn.

3

Đối với khung thông báo nhỏ của tôi bên trong ứng dụng Silverlight, tôi đang sử dụng mẫu Dàn xếp. Đó là một số loại xe buýt/môi giới nhắn tin, đối tượng nào đang đăng ký cho loại hoặc loại thông báo cụ thể. Sau đó, đối tượng hòa giải này (môi giới/xe buýt) đang quyết định ai sẽ nhận được loại tin nhắn nào.
someting như:

SubscribeFor<ChatMessage>().If(x=>x.SomeProp==true).Deliver(MyMethod); 

phương pháp mẫu được gọi là:

void MyMethod(ChatMessage msg) , or 
void MyMethod(BaseBessage msg) 

hoặc xuất bản (phát sóng) của thông điệp:

Publish(new ChatMessage()); 

BaseMessage là lớp trừu tượng, mà tất cả các thư kế thừa của tôi và chỉ tham chiếu đến người gửi và một số Guid duy nhất.

Tôi đã bắt đầu xây dựng khung thông báo của tôi từ MVVM Light Toolkit, bạn có thể xem mã nguồn của họ, nó không phức tạp!

Nếu bạn whish, tôi có thể đặt mã C# cho điều này ở đâu đó?

+0

Tôi rất thích xem mã này nếu bạn có thể chia sẻ. –

+0

@ByteBlast đây là thư viện tốt để triển khai trình phân giải Mediator: https://github.com/mhinze/ShortBus –

2

Mẫu gửi đi có thể hoạt động tốt.

public static class MessageDispatcher 
{ 
    private static readonly IMessageHandler s_DefaultHandler = 
     new DefaultMessageHandler(); 
    private static readonly Dictionary<Type, IMessageHandler> s_Handlers = 
     new Dictionary<Type, IMessageHandler>(); 

    static MessageDispatcher() 
    { 
    // Register a bunch of handlers. 
    s_Handlers.Add(typeof(OrderMessage), new OrderMessageHandler()); 
    s_Handlers.Add(typeof(TradeMessage), new TradeMessageHandler()); 
    } 

    public void Dispatch(IMessage msg) 
    { 
    Type key = msg.GetType(); 
    if (s_Handlers.ContainsKey(key)) 
    { 
     // We found a specific handler! :) 
     s_Handlers[key].Process(msg); 
    } 
    else 
    { 
     // We will have to resort to the default handler. :(
     s_DefaultHandler.Process(msg); 
    } 
    } 
} 

public interface IMessageHandler 
{ 
    void Process(IMessage msg); 
} 

public class OrderMessageHandler : IMessageHandler 
{ 
} 

public class TradeMessageHandler : IMessageHandler 
{ 
} 

Có tất cả các loại biến thể cho chủ đề này. Tất cả họ sẽ có một đối tượng điều phối có chứa nhiều trình xử lý khác nhau. Bạn nên xem xét một trình xử lý mặc định trong trường hợp người điều phối không thể tìm thấy một trình xử lý cụ thể. Có rất nhiều sự tự do trong cách bạn chọn để gửi các tin nhắn đến các trình xử lý thích hợp. Tôi chỉ xảy ra để gửi dựa trên loại, nhưng bạn có thể làm cho nó tùy ý phức tạp hơn. Có lẽ người điều phối có thể kiểm tra nội dung của tin nhắn để khám phá người xử lý tốt nhất. Có thể thông báo mang theo nó một khóa xác định trình xử lý ưu tiên. Tôi không biết. Có rất nhiều khả năng ở đây.

+0

Tôi thích ý tưởng này. Nhưng có 2 điều tôi không thích về việc thực hiện: Đầu tiên, nếu một trình xử lý được đăng ký cho loại ** A ** using _ContainsKey (Type) _ sẽ thất bại cho các đối tượng của bất kỳ lớp _derived_ nào từ ** A **. Thứ hai, một từ điển chỉ cho phép một trình xử lý cho mỗi loại thông điệp. – NVRAM

+0

@NVRAM: Điểm rất tốt. –

+0

@NVRAM - Tôi nghĩ bạn có thể vượt qua điểm đầu tiên bằng cách thêm các kiểu có nguồn gốc với cùng một trình xử lý. Rõ ràng không lý tưởng, nhưng là một công việc xung quanh. Điểm thứ hai là hợp lệ. – SwDevMan81

7

Từ kinh nghiệm của tôi về xử lý tin nhắn, thường là trường hợp người tiêu dùng khác nhau của tin nhắn yêu cầu xử lý nhiều loại thông báo khác nhau. Tôi đã tìm thấy mẫu Double Dispatch để xử lý điều này một cách độc đáo. Ý tưởng cơ bản là đăng ký một bộ xử lý gửi các thông báo nhận được tới trình xử lý để xử lý dựa trên loại cụ thể (sử dụng chức năng nạp chồng). Người tiêu dùng chỉ đăng ký các loại cụ thể mà họ muốn nhận. Dưới đây là sơ đồ lớp.

Double Dispatch UML Class Diagram

Mã này trông như thế này:

IHandler

public interface IHandler 
{ 
} 

IMessageHandler

public interface IMessageHandler<MessageType> : IHandler 
{ 
    void ProcessMessage(MessageType message); 
} 

IMessage

public interface IMessage 
{ 
    void Dispatch(IHandler handler); 
} 

MessageBase

public class MessageBase<MessageType> : IMessage 
    where MessageType : class, IMessage 
{ 
    public void Dispatch(IHandler handler) 
    { 
     MessageType msg_as_msg_type = this as MessageType; 
     if (msg_as_msg_type != null) 
     { 
     DynamicDispatch(handler, msg_as_msg_type); 
     } 
    } 

    protected void DynamicDispatch(IHandler handler, MessageType self) 
    { 
     IMessageHandler<MessageType> handlerTarget = 
     handler as IMessageHandler<MessageType>; 
     if (handlerTarget != null) 
     { 
     handlerTarget.ProcessMessage(self); 
     } 
    } 
} 

DerivedMessageHandlerOne

// Consumer of DerivedMessageOne and DerivedMessageTwo 
// (some task or process that wants to receive messages) 
public class DerivedMessageHandlerOne : 
    IMessageHandler<DerivedMessageOne>, 
    IMessageHandler<DerivedMessageTwo> 
    // Just add handlers here to process incoming messages 
{  
    public DerivedMessageHandlerOne() { } 

    #region IMessageHandler<MessaegType> Members 

    // ************ handle both messages *************** // 
    public void ProcessMessage(DerivedMessageOne message) 
    { 
    // Received Message one, do something with it 
    } 

    public void ProcessMessage(DerivedMessageTwo message) 
    { 
     // Received Message two, do something with it 
    } 

    #endregion 
} 

DerivedMessageOne

public class DerivedMessageOne : MessageBase<DerivedMessageOne> 
{ 
    public int MessageOneField; 

    public DerivedMessageOne() { } 
} 

Sau đó, bạn chỉ có một container quản lý các Handlers và bạn là tất cả bộ. Một đơn giản cho vòng lặp qua danh sách các Handlers khi một tin nhắn nhận được, và Handlers nhận các thông điệp mà họ muốn họ

// Receive some message and dispatch it to listeners 
IMessage message_received = ... 
foreach(IHandler handler in mListOfRegisteredHandlers) 
{ 
    message_received.Dispatch(handler); 
} 

Thiết kế này ra khỏi một câu hỏi tôi hỏi một lúc lại về Polymorphic Event Handling

+0

Bạn có ý nghĩa gì bởi _container_ chính xác? –

+0

@ByteBlast - Bởi container Tôi chỉ có nghĩa là một lớp học mananges bộ sưu tập của 'mListOfRegisteredHandlers'. Về cơ bản, một lớp có thêm/gỡ bỏ các hàm điều khiển và có thể có một hàm trợ giúp gọi là 'DispatchMessage (IMessage msg)' sẽ lặp qua danh sách 'mListOfRegisteredHandlers' và gọi' msg.Dispatch (handler) '(mã cuối cùng) đoạn trích trong câu trả lời của tôi). – SwDevMan81

+0

Bạn sẽ gọi loại container này là gì? Không quan tâm - cảm ơn. –

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