Aspect::Library::Listenable(3pm) | User Contributed Perl Documentation | Aspect::Library::Listenable(3pm) |
Aspect::Library::Listenable - Observer pattern with events
# The class that we will make listenable package Point; sub new { bless { color => 'blue' }, shift; } sub erase { print 'erased!'; } sub get_color { $_[0]->{color}; } sub set_color { $_[0]->{color} = $_[1]; } package main; use Aspect; use Aspect::Library::Listenable; # Setup the simplest listenable relationship: a signal # Define the aspect for the listenable relationship aspect Listenable => ( Erase => call 'Point::erase' ); # Now add a listener my $erase_listener = sub { print shift->as_string }; add_listener $point, Erase => $erase_listener; my $point = Point->new; $point->erase; # prints: "erased! name:Erase, source:Point" remove_listener $point, Erase => $erase_listener; $point->erase; # prints: "erased!" # A more complex relationship: listeners get old and new color values # and will only be notified if these values are not equal aspect Listenable => (Color => call 'Point::set_color', color => 'get_color'); add_listener $point, Color => my $color_listener = sub { print shift->as_string }; $point->set_color('red'); # prints: "name:Color, source:Point, color:red, old_color:blue, params:red" $point->set_color('red'); # does not print anything, color unchanged remove_listener $point, Color => $color_listener; # listeners can be callback, as above, or they can be objects package ColorListener; sub new { bless {}, shift } sub handle_event_Color { print "new color: ". shift->color }; package main; add_listener $point, Color => my $object_listener = ColorListener->new; $point->set_color('green'); # prints: "new color: green" remove_listener $point, Color => $object_listener; # listeners can also be specific methods on objects package EraseListener; sub new { bless {}, shift } sub my_erase_handler { print 'heard an erase event!' } package main; add_listener $point, Color => [my_erase_handler => my $method_listener = EraseListener->new] $point->erase; # prints: "heard an erase event!" remove_listener $point, Color => $method_listener;
A reusable aspect for implementing the Listenable design pattern. It lets you to define listenables and the events they fire. Then you can add/remove listeners to these listenables. When specific methods of the listenable are called, registered listeners will be notified.
Some examples of use are:
The Listenable pattern is a variation of the basic Observer pattern:
Because it is implemented using aspects, there is no change required to the listenable or listener classes. For example, you are not required to fire events after performing interesting state changes in the listenable. The aspect will do this for you.
Creating listenable relationships between objects is done in two steps. First you must define the relationship between the classes, then you can instantiate the defined relationship between instances of these classes.
Defining the relationships between classes is done once per program run. This is similar to how methods and classes are defined only once.
Each listenable relationship between classes is defined by one aspect, answering 3 questions:
You create a listenable aspect so:
aspect Listenable => (EVENT_NAME => POINTCUT, EVENT_DATA)
The "EVENT_DATA" part is optional. The three parameters are your answers to the questions above:
Here is an example of transforming a selector widget, so that it will fire an event, right after it has received a click from the user. Listeners can get the selected index from the event they receive:
aspect Listenable => ( ItemSelected => call 'SelectorWidget::click', selected_index => 'selected_index', );
This assumes that there exists a method "SelectorWidget::selected_index", that will return the currently selected item, and a method "click", called whenever the user clicks the widget. The event will only be fired if the "selected_index" has changed.
Because the aspect should be created only Once during a program run, for each listenable relationship type, there are several options for choosing the place to actually create it:
Now all that is needed is some way to add and remove listener objects, from a specific listenable, so that the event will actually be handled by someone, and not just fired into the void.
The simplest listener is a "CODE" ref. It can added and removed so:
use Aspect::Library::Listenable; my $code = sub { print "event!" } add_listener $point, Color => $code; # add $point->set_color('red'); # $code will be run remove_listener $point, Color => $code; # remove $point->set_color('yellow'); # event will not fire
The event object is the only parameter received by the callback.
The other two types of listeners are object, and method:
When the listener is an object , the method name to be called is computed from the event name by adding "handle_event_" in front of the event name. For example: a car object will call the method "handle_event_FrontLeftDoorOpened" on its listeners that are objects.
When the listener is an array ref (method listener), the method name (1st element) is called on the object (2nd element). When removing this type of listener, you do not remove the array ref but the listener object, i.e. exactly like you remove an object listener.
For method listeners, you can also change the parameter list of the method. Usually, the event is the only parameter to the listener method. By changing the parameter list, you can turn any existing method into a listener method, without changing it.
You change the parameter list, by providing a list of event properties, whose values will become the new parameter list. Here is how to make a "Family::set_father_name" run each time "Person::set_name" is called on the father object:
aspect Listenable => ( NameChange => call 'Person::set_name', name => 'name', ); $father = Person->new; $family = Family->new; add_listener $father, NameChange => [set_father_name => $family, [qw(name)]]; $father->set_name('dan'); # $family->set_father_name('dan') will be called
Listener code is called with one parameter: the event. Its class is "Aspect::Listenable::Event". All events have at least these properties:
Besides these properties, you can also access any properties that were defined to be in the event state, when the listenable aspect was created. For each such property, there is another, with "old_" prefixed, which holds the value of the property on the listenable, before the event was fired.
You access properties on the event using getters. To get the new color of a point after a "Color" event:
sub handle_event_Color { my $event = shift; print $event->color; }
"Class::Listener", "Class::Observable". Both are object-oriented solutions to the same problem. Both force you to change the listenable class, by adding the code to fire events inside your "hot" methods.
Adam Kennedy <adamk@cpan.org>
Marcel Grünauer <marcel@cpan.org>
Ran Eilam <eilara@cpan.org>
Copyright 2001 by Marcel Grünauer
Some parts copyright 2009 - 2013 Adam Kennedy.
This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
2022-06-08 | perl v5.34.0 |