NAME

Class::Publisher - A simple publish-subscribe event framework

SYNOPSIS

# Define a class that publishes events
package My::Widget;
use base 'Class::Publisher';

# Publish event
sub incriment {
    my ($self) = @_;
    my $old_value = $self->{value}++;
    $self->notify_subscribers('changed', old_value => $old_value);
}

# Define a subscriber;
package My::Subscriber;

sub new {
    my $self = bless {}, shift;

    # Subscribe to events from My::Widget
    My::Widget->add_subscriber('changed', sub {$self->_widgetvalue(@_)});

    # Subscribe to all events from My::Widget
    My::Widget->add_subscriber('*', \&_on_update_widget);

    return $self;
}

sub _widgetvalue {
    my $self = shift;
    my ($item, $event, %params) = @_;

    # do something with new/old value
}

sub _on_update_widget {
    my ($item, $event, %params) = @_;
    print STDERR "Caught event $event from '$item'\n";
}

DESCRIPTION

Class::Publisher impliments the Publish-Subscribe pattern (also referred to as the Observer pattern). It is often used in user interfaces when many entities are interested in a particular event. The Publish-Subscribe pattern decouples the publisher and subscriber and provides a generic interface for publishing and subscribing to events.

This module is based on Class::Observable by Chris Winters. The main difference is that entities can subscribe to specific events raised by the publisher rather than receiving all notifications.

Like Class::Observable, entities can subscribe to events raised by a publisher class or a publisher instance:

My::Widget->add_subscriber('update', \&_on_widget_update);
$widget->('update', sub {print STDERR "$widget raised update event"});

Publisher classes and objects

The publisher does not need to implement any extra methods or variables. Whenever it wants to let subscribers know about an event, it just needs to call notify_subscribers().

As noted above, it does not matter if the publisher is a class or object -- the behavior is the same. The difference comes in determining which subscribers are to be notified:

  • If the publisher is a class, all objects instantiated from that class will use these subscribers. In addition, all subclasses and objects instantiated from the subclasses will use these subscribers.

  • If the publisher is an object, only that particular object will use its subscribers. Once it falls out of scope then the subscribers will no longer be available. (See "Publisher Objects and DESTROY" below.)

Subscribers

There are three types of subscribers: classes, objects and subroutines. They all respond to events raised by the publisher's notify_subscribers() method.

The following parameters are passed to subscribers:

  • The publisher class or object that generated the event

  • The name of the event or '*' if no event was defined for the event

  • Additional parameters passed to the notify_subscribers() method

Class and object subscribers differ slightly by being passed their class name/object an additional parameter before the publisher item

Class subscribers

Class subscribers are notified of events via the class's update() method:

package My::Subscriber;

sub update {
    my ($class, $publisher, $event, @args) = @_;
    if ($event eq 'reload') {
        # ...
    } elsif ($event eq 'refresh') {
    }
    # ...
}

Class notifications can be routed to other methods. See add_subscriber().

Object subscribers

Object subscribers are notified of events via the object's update() method:

package My::Subscriber;

sub update {
    my ($self, $publisher, $event, @args) = @_;
    # ...
}

Object notifications can be routed to other methods. See add_subscriber().

Subroutine subscribers
package My::Subscriber;

sub _refresh {
    my ($publisher, $event, @args) = @_;
    # ...
}

sub _reload {
    my $self = shift;
    my ($publisher, $event, @args) = @_;
    # ...
}

sub _catch_all {
    # ...
}

My::Publisher->add_subscriber('_refresh', \&_refresh);
My::Publisher->add_subscriber('_refresh', sub {$self->_reload(@_)});
My::Publisher->add_subscriber('*', \&_catch_all);

Publisher Objects and DESTROY

One problem with this module relates to subscribed objects. Once the publisher goes out of scope, its subscribers will still be hanging around. For one-off scripts this is not a problem, but for long-lived processes this could be a memory leak.

To take care of this, it is a good idea to explicitly release subscribers attached to an object in the DESTROY method. This should suffice:

sub DESTROY {
    my ( $self ) = @_;
    $self->delete_all_subscribers;
}

METHODS

add_subscriber($event, $subscriber, [$method_name])

Registers a subscriber to receive event notifications on the publisher. Each subscriber can be a class name, object or subroutine -- see Subscribers.

Object and class notifications can be routed to $method_name if defined. Otherwise the default update() method will be called.

If $event is undefined or '', the subscribers will be subscribed to the special event '*' that receives notifications of all events. Otherwise, the subscribers will only receive notifications when the notification event matches $event.

Returns: The number of subscribers of the given topic.

notify_subscribers([$event], @params)

Notify subscribers of a event.

$event and @params are optional. If a $event is given, subscribers to that event are notified. Subscribers to '*' (all events) are always notified.

delete_subscriber([$event], $subscriber)

Unsubscribes a subscriber from the given event on the publishing item.

If $event is undefined, all subscriptions will be cancelled for the subscribers.

Returns: The number of remaining subscribers of the given topic.

delete_all_subscribers()

Unsubscribes all subscribers from the publisher.

Returns: the number of subscribers removed

get_subscribers([$event])

Return the subscribers for the given event. All subscribers for all events will be returned if no event is given.

copy_subscribers($to_object)

Copy subscribers to another publisher item.

Returns: the number of subscribers copied

count_subscribers([$count])

Return a count of the subscribers for the given event (or all subscribers of no event was given).

DEBUGGING

Class::Publisher has Log::Trace hooks. You can enable debugging with a statement like this:

use Log::Trace warn => {Deep => 1, Match => 'Class::Publisher'};

See Log::Trace for more options

AUTHOR

Simon Flack

Based quite heavily on Class::Observable by Chris Winters

LICENSE

Class::Publisher is free software which you can redistribute and/or modify under the same terms as Perl itself.

SEE ALSO

Class::Observable

Class::ISA

Class::Trigger

Aspect