www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Defining event handlers for function, method, or shared method

reply tcak <1ltkrs+3wyh1ow7kzn1k sharklasers.com> writes:
In many multi threading module designs of mine, I generally 
design a base class, and
this class have some events. Exempli gratia:

void eventOnStart();
void eventOnStop();
void eventOnItemAdded( size_t itemIndex );

There is this problem though. I need/want this class to be able 
to bind a function, a method, or a shared method. From the 
perspective of class design, there shouldn't be any
difference. Its purpose is to let know about the event, not to 
care about how the event
handler is designed.

If I want handlers to be functions, I design it like,

public alias EventOnStart = void function();
public EventOnStart eventOnStart;


If it is for normal methods, design becomes like,

public alias EventOnStart = void delegate();


For shared methods, it becomes,

public alias EventOnStart = void delegate() shared;


As you will guess, to be able to support any of those three, it 
becomes so complex. Is
there any way generalise to support three of them without making 
this any complex? A
secondary thing, this is not D related though, whether there is 
any different approach
for event listener design like Observer pattern but with little 
overhead and complexity?
Jan 26
parent reply =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 01/26/2016 10:41 AM, tcak wrote:
 I need/want this class to be able to bind
 a function, a method, or a shared method. From the perspective of class
 design, there shouldn't be any
 difference. Its purpose is to let know about the event, not to care
 about how the event
 handler is designed.
If I understand the problem correctly, an interface can define the interface and a templated class can provide the differences: import std.stdio; import std.algorithm; interface Event { void start(); void stop(); void itemAdded( size_t itemIndex ); } class ConcreteEvent(alias onStart, alias onStop, alias onItemAdded) : Event { void start() { onStart(); } void stop() { onStop(); } void itemAdded(size_t itemIndex) { itemAdded(itemIndex); } } void fooStart() { } void fooStop() { } void fooItemAdded(size_t itemIndex) { } void bar(size_t itemIndex) { } void main() { Event[] events; events ~= new ConcreteEvent!(fooStart, fooStop, fooItemAdded); struct S { void memberFunction() { } } auto s = S(); auto memberClosure(ref S s) {On 01/26/2016 10:41 AM, tcak wrote:
 I need/want this class to be able to bind
 a function, a method, or a shared method. From the perspective of class
 design, there shouldn't be any
 difference. Its purpose is to let know about the event, not to care
 about how the event
 handler is designed.
If I understand the problem correctly, an interface can define the interface and a templated class can provide differences: import std.stdio; import std.algorithm; interface Event { void start(); void stop(); void itemAdded( size_t itemIndex ); } class ConcreteEvent(alias onStart, alias onStop, alias onItemAdded) : Event { void start() { onStart(); } void stop() { onStop(); } void itemAdded(size_t itemIndex) { itemAdded(itemIndex); } } void fooStart() { } void fooStop() { } void fooItemAdded(size_t itemIndex) { } void bar(size_t itemIndex) { } void main() { Event[] events; events ~= new ConcreteEvent!(fooStart, fooStop, fooItemAdded); struct S { void memberFunction() { } } auto s = S(); auto memberClosure(ref S s) { return () => s.memberFunction(); } events ~= new ConcreteEvent!(() => memberClosure(s), () => writeln("stop"), bar); events.each!(e => e.stop); } Ali return () => s.memberFunction(); } events ~= new ConcreteEvent!(() => memberClosure(s), () => writeln("stop"), bar); events.each!(e => e.stop); } Ali
Jan 26
next sibling parent =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
What a lousy copy+paste mistake that was. I am glad no credit card 
number leaked there. :p

On 01/26/2016 11:22 AM, Ali Çehreli wrote:
 class ConcreteEvent(alias onStart, alias onStop, alias onItemAdded) :
      void itemAdded(size_t itemIndex) {
          itemAdded(itemIndex);
      }
That call should have been onItemAdded(). Ali
Jan 26
prev sibling parent reply tcak <1ltkrs+3wyh1ow7kzn1k sharklasers.com> writes:
On Tuesday, 26 January 2016 at 19:22:58 UTC, Ali Çehreli wrote:
 On 01/26/2016 10:41 AM, tcak wrote:
 I need/want this class to be able to bind
 a function, a method, or a shared method. From the
perspective of class
 design, there shouldn't be any
 difference. Its purpose is to let know about the event, not
to care
 about how the event
 handler is designed.
If I understand the problem correctly, an interface can define the interface and a templated class can provide the differences: import std.stdio; import std.algorithm; interface Event { void start(); void stop(); void itemAdded( size_t itemIndex ); } class ConcreteEvent(alias onStart, alias onStop, alias onItemAdded) : Event { void start() { onStart(); } void stop() { onStop(); } void itemAdded(size_t itemIndex) { itemAdded(itemIndex); } } void fooStart() { } void fooStop() { } void fooItemAdded(size_t itemIndex) { } void bar(size_t itemIndex) { } void main() { Event[] events; events ~= new ConcreteEvent!(fooStart, fooStop, fooItemAdded); struct S { void memberFunction() { } } auto s = S(); auto memberClosure(ref S s) {On 01/26/2016 10:41 AM, tcak wrote:
 I need/want this class to be able to bind
 a function, a method, or a shared method. From the
perspective of class
 design, there shouldn't be any
 difference. Its purpose is to let know about the event, not
to care
 about how the event
 handler is designed.
If I understand the problem correctly, an interface can define the interface and a templated class can provide differences: import std.stdio; import std.algorithm; interface Event { void start(); void stop(); void itemAdded( size_t itemIndex ); } class ConcreteEvent(alias onStart, alias onStop, alias onItemAdded) : Event { void start() { onStart(); } void stop() { onStop(); } void itemAdded(size_t itemIndex) { itemAdded(itemIndex); } } void fooStart() { } void fooStop() { } void fooItemAdded(size_t itemIndex) { } void bar(size_t itemIndex) { } void main() { Event[] events; events ~= new ConcreteEvent!(fooStart, fooStop, fooItemAdded); struct S { void memberFunction() { } } auto s = S(); auto memberClosure(ref S s) { return () => s.memberFunction(); } events ~= new ConcreteEvent!(() => memberClosure(s), () => writeln("stop"), bar); events.each!(e => e.stop); } Ali return () => s.memberFunction(); } events ~= new ConcreteEvent!(() => memberClosure(s), () => writeln("stop"), bar); events.each!(e => e.stop); } Ali
Hmm. Your example works fine for functions, but I can't pass a method instead of function as alias. Check my example: import std.socket; class EventClass{ public void eventHandlerMethod(){ writeln("Barking from method"); } } class Generator(alias eventHandler){ public void bark(){ eventHandler(); } } public void eventHandlerFunc(){ writeln("Barking from function"); } void main(){ auto events = new EventClass; auto gen1 = new Generator!( eventHandlerFunc )(); auto gen2 = new Generator!( events.eventHandlerMethod )(); gen1.bark(); gen2.bark(); } Error is given on "auto gen2 = ..." in main function due to passing method. I guess because it is runtime normal compile time information. I was looking for something like to be defined in the class Generator: public DelegateOnStart eventOnStart; So I could set eventOnStart as either function pointer, method pointer, or shared method pointer. To be able to support three of them, for every event, I need to define another variable, another alias, and while calling, check whichever variable (delegate pointer) is set, and call that one. I hope I am making it clear why it turns into mess.
Jan 26
next sibling parent tcak <1ltkrs+3wyh1ow7kzn1k sharklasers.com> writes:
On Tuesday, 26 January 2016 at 19:42:42 UTC, tcak wrote:
 On Tuesday, 26 January 2016 at 19:22:58 UTC, Ali Çehreli wrote:
 [...]
Hmm. Your example works fine for functions, but I can't pass a method instead of function as alias. Check my example: [...]
Edit: ... "I guess because it is runtime information, not compile time" ...
Jan 26
prev sibling parent =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 01/26/2016 11:42 AM, tcak wrote:

     struct S {
         void memberFunction() {
         }
     }
     auto s = S();

     auto memberClosure(ref S s) {
         return () => s.memberFunction();
     }

     events ~= new ConcreteEvent!(() => memberClosure(s),
                                  () => writeln("stop"),
                                  bar);

     events.each!(e => e.stop);
 }

 Ali
 Hmm. Your example works fine for functions, but I can't pass a method
 instead of function as alias.
That's why I used a closure in the example.
 Check my example:


 import std.socket;

 class EventClass{
      public void eventHandlerMethod(){
          writeln("Barking from method");
      }
 }

 class Generator(alias eventHandler){
      public void bark(){
          eventHandler();
      }
 }

 public void eventHandlerFunc(){
      writeln("Barking from function");
 }

 void main(){
      auto events = new EventClass;

      auto gen1 = new Generator!( eventHandlerFunc )();
      auto gen2 = new Generator!( events.eventHandlerMethod )();
As far as I know, just the member function name cannot be used that way. The problem boils down to how to create a type (or an alias) that will call a specific member function of a given object. One way is to have a dedicated type that always call foo(): import std.stdio; class C { void foo() { writeln("foo called"); } } class FooCaller { C c; this (C c) { this.c = c; } void call() { c.foo(); // hard-coded to call foo() } } void main() { auto f = new FooCaller(new C()); f.call(); } The following one creates a lambda (closure in this case?) that allows the user to pick which member function to call. Admittedly, CallableCaller() is not really needed in this example but it demonstrates how "a member function call on a specific object" can be passed as an alias template parameter: import std.stdio; class C { void foo() { writeln("foo called"); } } class CallableCaller(alias closure) { void call() { closure(); } } void main() { auto c = new C(); auto f = new CallableCaller!(() => c.foo()); f.call(); } Ali
Jan 26