Jump to content
Dmitry Onoshko

Best option for events with different parameter type in each child class?

Recommended Posts

Posted (edited)

Suppose there’re two TCP-based protocols that should be implemented with the following gotchas in mind:

  • any message might get broken into pieces while passing through multiple routers;
  • there might not only be well-formed messages, but ill-formed messages and garbage data as well.

 

The logic of extracting messages from a TCP stream can quite easily be abstracted and implemented only once, in a base class (say, TCustomProtocol) that might only use a few virtual abstract methods to perform protocol-specific processing. Then there would be two descendant classes that implement those methods. Note also, that extracting those messages involves actually parsing and “decoding” the messages, as part of the extraction.

 

Now the problem is that whenever a well-formed message is extracted from the stream, an event (say, OnMessage) should be fired passing the parsed message data to the event handler. The point where the event should get fired is obviously in the base class, but the types used to represent the parsed messages are very different due to the nature of those protocols. So, defining the class members for the event in the base class is problematic.

 

I can see several solutions:

  1. Define the message parameter of OnMessage in a very generic way: untyped parameter or a (Pointer, Size) pair. This removes type checking from the scene, though, and requires the event handler to do type casting.
  2. Define the base class as a parameterized (generic) class: TCustomProtocol<TMessageType>. Then derive TFooProtocol and TBarProtocol using TCustomProtocol<TFooMsg> and TCustomProtocol<TBarMsg> as base classes, define the OnMessage type using TMessageType. This solution is almost good but leaves OnMessage (and any other event define in the base class) with the ugly TCustomProtocol<TMessageType> parameter that the users of TFooProtocol and TBarProtocol should write defining their event handlers.
  3. Write all the OnMessage-related stuff separately in descendants. Doubles the amount of code for the stuff that is otherwise the same.

 

I wonder, how would/do you solve the problem with modern Delphi? Thanks in advance for your answers.

Edited by Dmitry Onoshko

Share this post


Link to post
1 hour ago, Dmitry Onoshko said:

but leaves OnMessage (and any other event define in the base class) with the ugly TCustomProtocol<TMessageType> parameter that the users of TFooProtocol and TBarProtocol should write defining their event handlers.

What hinders you to declare your own types for that? Then you can write your event handler methods just like before.

type
  TFooProtocol = TCustomProtocol<TFooMsg>;
  TBarProtocol = TCustomProtocol<TBarMsg>;

 

Share this post


Link to post
25 minutes ago, Uwe Raabe said:

What hinders you to declare your own types for that? Then you can write your event handler methods just like before.


type
  TFooProtocol = TCustomProtocol<TFooMsg>;
  TBarProtocol = TCustomProtocol<TBarMsg>;

 

Well, the second approach actually starts with TFooProtocol = class(TCustomProtocol<TFooMsg>) and TBarProtocol = class(TCustomProtocol<TBarMsg>), since the descendants have to implement abstract methods from the base class. So, the names are already used. And since OnMessage should be defined in the base class, and the base class should normally know nothing about its descendants, I can see no way to use such type definitions without breaking the concepts of OOP.

Share this post


Link to post

Perhaps something like this?

type
  TFooMessage = class
    Foo: Integer;
  end;
  TBarMessage = class
    Bar: Double;
  end;

  TCustomProtocol<T> = class(TPersistent)
  private
    FMsg: T;
    FOnMsg: TProc<T>;
  published
    property OnMsg: TProc<T> read FOnMsg write FOnMsg;
  end;

  TFooProtocol = class(TCustomProtocol<TFooMessage>);
  TBarProtocol = class(TCustomProtocol<TBarMessage>);

 

Share this post


Link to post
Posted (edited)

Sorry, I failed to state that one of the parameters of the event handler should be the protocol object that extracted the message (so that the event handler can perform certain actions on the object), that’s the reason I complain about the ugly TCustomProtocol<TMessageType> argument.

9 minutes ago, JonRobertson said:

Perhaps something like this?

So, yes, that’s my approach number 2, but the disadvantage is that ugly argument type then.

Edited by Dmitry Onoshko

Share this post


Link to post
7 minutes ago, Uwe Raabe said:

Can you show some code - even when ugly?

type
  TCustomProtocol<TMessageType> = class
  type
    TMessageEvent = procedure(Sender: TCustomProtocol<TMessageType>; const AMessage: TMessageType) of object;
  private
    FOnMessage: TMessageEvent;
    ...
  end;

  TCoolProtocol = class(TCustomProtocol<TCoolMessage>)
    ...
  end;

...

procedure TCoolServer.MessageReceived(Sender: TCustomProtocol<TCoolMessage>;
  const AMessage: TCoolMessage);
var
  Proto: TCoolProtocol absolute Sender;
begin
  // Handling goes here
end;

The first parameter here has a long type specification which actually contains two type names. Besides, the procedure header basically repeats itself: one TCoolMessage would be enough to understand what’s going on.

 

Hmmm, I’ve just felt that the wider problem here is that there seems to be no way to define the event in such a way that Sender type is always the descendant that can extract the message. That would also allow the handler to get access to all the methods provided by the specific descendant.

 

Anyway, the generic-based approach works, it just doesn’t seem nice enough.

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×