Class::Observable(3)

πŸ“¦
version 2.004 Β· 2 versions
, in fedora 43

πŸ’– Class::Observable(3) πŸ’–

okay so bestie, welcome to Class::Observable(3) from the User Contributed Perl Documentation. this is the manual page for a Perl module that lets other classes and objects respond to events in yours. no cap, it's giving observer pattern realness. let's get into it. 🫢

✨ NAME ✨

  • Class::Observable β€” Allow other classes and objects to respond to events in yours.

🌈 SYNOPSIS 🌈

bestie, here's the whole vibe in code. copy it, run it, live it. πŸ’…

# Define an observable class

package My::Object;

use parent qw( Class::Observable );

# Tell all classes/objects observing this object that a state-change
# has occurred

sub create {
   my ( $self ) = @_;
   eval { $self->_perform_create() };
   if ( $@ ) {
       My::Exception->throw( "Error saving: $@" );
   }
   $self->notify_observers();
}

# Same thing, except make the type of change explicit and pass
# arguments.

sub edit {
   my ( $self ) = @_;
   my %old_values = $self->extract_values;
   eval { $self->_perform_edit() };
   if ( $@ ) {
       My::Exception->throw( "Error saving: $@" );
   }
   $self->notify_observers( 'edit', old_values => \%old_values );
}

# Define an observer

package My::Observer;

sub update {
   my ( $class, $object, $action ) = @_;
   unless ( $action ) {
       warn "Cannot operation on [", $object->id, "] without action";
       return;
   }
   $class->_on_save( $object )   if ( $action eq 'save' );
   $class->_on_update( $object ) if ( $action eq 'update' );
}

# Register the observer class with all instances of the observable
# class

My::Object->add_observer( 'My::Observer' );

# Register the observer class with a single instance of the
# observable class

my $object = My::Object->new( 'foo' );
$object->add_observer( 'My::Observer' );

# Register an observer object the same way

my $observer = My::Observer->new( 'bar' );
My::Object->add_observer( $observer );
my $object = My::Object->new( 'foo' );
$object->add_observer( $observer );

# Register an observer using a subroutine

sub catch_observation { ... }

My::Object->add_observer( \&catch_observation );
my $object = My::Object->new( 'foo' );
$object->add_observer( \&catch_observation );

# Define the observable class as a parent and allow the observers to
# be used by the child

package My::Parent;

use strict;
use parent qw( Class::Observable );

sub prepare_for_bed {
    my ( $self ) = @_;
    $self->notify_observers( 'prepare_for_bed' );
}

sub brush_teeth {
    my ( $self ) = @_;
    $self->_brush_teeth( time => 45 );
    $self->_floss_teeth( time => 30 );
    $self->_gargle( time => 30 );
}

sub wash_face { ... }

package My::Child;

use strict;
use parent qw( My::Parent );

sub brush_teeth {
    my ( $self ) = @_;
    $self->_wet_toothbrush();
}

sub wash_face { return }

# Create a class-based observer

package My::ParentRules;

sub update {
    my ( $item, $action ) = @_;
    if ( $action eq 'prepare_for_bed' ) {
        $item->brush_teeth;
        $item->wash_face;
    }
}

My::Parent->add_observer( __PACKAGE__ );

$parent->prepare_for_bed # brush, floss, gargle, and wash face
$child->prepare_for_bed  # pretend to brush, pretend to wash face

🧚 DESCRIPTION 🧚

okay so if you've ever messed with Java, you might have bumped into the "java.util.Observable" class and the "java.util.Observer" interface. with those, you can decouple an object from the one or more objects that wanna be notified whenever particular events happen. it's giving separation of concerns, periodt.

these events happen based on a contract with the observed item. they can pop off at the beginning, in the middle, or at the end of a method. plus, the object knows it's being observed. it just doesn't know how many objects are watching or what types they are. so it can control when the messages get sent to the observers. main character energy, honestly.

the behavior of the observers is totally up to you. BUT bestie, be aware: we do not do any error handling from calls to the observers. if an observer throws a "die", it will bubble up to the observed item and require handling there. so be careful. no cap. πŸ’€

🌸 USER GUIDE 🌸

okay so throughout this whole doc, when we say 'observed item' or 'observable item', we're being a little ambiguous on purpose. it just means that both a class and an object can be observed. the behavior when notifying observers is identical. the only difference is which observers get notified. (see "Observable Classes and Objects" for the tea.) β˜•

πŸ¦‹ Observable Classes and Objects πŸ¦‹

the observable item doesn't need to implement any extra methods or variables. whenever it wants to let observers know about a state-change or occurrence in the object, it just needs to call notify_observers(). that's it. periodt.

like we said, whether the observed item is a class or object doesn't matter β€” the behavior is the same. the difference is in figuring out which observers are to be notified:

  • If the observed item is a class, all objects instantiated from that class will use these observers. plus, all subclasses and objects instantiated from the subclasses will use these observers. it's giving inheritance realness.
  • If the observed item is an object, only that particular object will use its observers. once it falls out of scope, then the observers will no longer be available. (see "Observable Objects and DESTROY" below.) so don't get attached. 😭

whichever you chose, your documentation should make clear which type of observed item observers can expect. be a good communicator, bestie.

so given the following example:

BEGIN {
    package Foo;
    use parent qw( Class::Observable );
    sub new { return bless( {}, $_[0] ) }
    sub yodel { $_[0]->notify_observers }

    package Baz;
    use parent qw( Foo );
    sub yell { $_[0]->notify_observers }
}

sub observer_a { print "Observation A from [$_[0]]\n" }
sub observer_b { print "Observation B from [$_[0]]\n" }
sub observer_c { print "Observation C from [$_[0]]\n" }

Foo->add_observer( \&observer_a );
Baz->add_observer( \&observer_b );

my $foo = Foo->new;
print "Yodeling...\n";
$foo->yodel;

my $baz_a = Baz->new;
print "Yelling A...\n";
$baz_a->yell;

my $baz_b = Baz->new;
$baz_b->add_observer( \&observer_c );
print "Yelling B...\n";
$baz_b->yell;

you would see something like

Yodeling...
Observation A from [Foo=HASH(0x80f7acc)]
Yelling A...
Observation B from [Baz=HASH(0x815c2b4)]
Observation A from [Baz=HASH(0x815c2b4)]
Yelling B...
Observation C from [Baz=HASH(0x815c344)]
Observation B from [Baz=HASH(0x815c344)]
Observation A from [Baz=HASH(0x815c344)]

and since "Bar" is a child of "Foo" and each has one class-level observer, running either:

my @observers = Baz->get_observers();
my @observers = $baz_a->get_observers();

would return a two-item list. the first item would be the "observer_b" code reference, the second the "observer_a" code reference. running:

my @observers = $baz_b->get_observers();

would return a three-item list, including the observer for that specific object ("observer_c" coderef) as well as from its class (Baz) and the parent (Foo) of its class. slay. πŸ’…

πŸŽ€ Types of Observers πŸŽ€

there are three types of observers: classes, objects, and subroutines. all three respond to events when notify_observers() is called from an observable item. the differences among the three are:

  • A class or object observer must implement a method update() which is called when a state-change occurs. the name of the subroutine observer is irrelevant. (so you can name it whatever, bestie.)
  • A class or object observer must take at least two arguments: itself and the observed item. the subroutine observer is obligated to take only one argument, the observed item. Both types of observers may also take an action name and a hashref of parameters as optional arguments. whether these are used depends on the observed item.
  • Object observers can maintain state between responding to observations. it's giving memory. 🧠

Examples:

Subroutine observer:

sub respond {
    my ( $item, $action, $params ) = @_;
    return unless ( $action eq 'update' );
    # ...
}
$observable->add_observer( \&respond );

Class observer:

package My::ObserverC;

sub update {
    my ( $class, $item, $action, $params ) = @_;
    return unless ( $action eq 'update' );
    # ...
}

Object observer:

package My::ObserverO;

sub new {
    my ( $class, $type ) = @_;
    return bless ( { type => $type }, $class );
}

sub update {
    my ( $self, $item, $action, $params ) = @_;
    return unless ( $action eq $self->{type} );
    # ...
}

πŸ’€ Observable Objects and DESTROY πŸ’€

this class has a "DESTROY" method which must run when an instance of an observable class goes out of scope in order to clean up the observers added to that instance. no cap.

if there is no other destructor in the inheritance tree, this will end up happening naturally and everything will be fine. slay.

if it does not get called, then the list of observers will leak (which also prevents the observers in it from being garbage-collected) and may become associated with a different instance created later at the same memory address as a previous instance. that's giving memory leak nightmare. πŸ’€

this may happen if a class needs its own "DESTROY" method when it also wants to inherit from Class::Observer (even indirectly!), because perl only invokes the single nearest inherited "DESTROY". (yes, Class::Observer, we're keeping it exactly as written, bestie.)

the most straightforward (but maybe not best) way to ensure that the destructor is called is to do something like this:

# in My::Class
sub DESTROY {
    # ...
    $self->Class::Observable::DESTROY;
    # ...
}

a better way may be to write all destructors in your class hierarchy with the expectation that all of them will be called (which would usually be preferred anyway) and then enforcing that expectation by writing all of them as follows:

use mro;
sub DESTROY {
    # ...
    $self->maybe::next::method;
    # ...
}

(perl being perl, of course, there are many other ways to go about this. iykyk.)

✨ METHODS ✨

Okay so bestie, this is where the real magic happens. These are the methods you'll be calling on your observed items. Periodt.

notify_observers( [ $action, @params ] )

Called from the observed item, this method sends a message to all observers that a state-change has occurred. The observed item can optionally include additional information about the type of change that has occurred, and any additional parameters @params which get passed along to each observer. The observed item should indicate in its API what information will be passed along to the observers in $action and @params.

Returns: Nothing

Example:

sub remove {
    my ( $self ) = @_;
    eval { $self->_remove_item_from_datastore };
    if ( $@ ) {
        $self->notify_observers( 'remove-fail', error_message => $@ );
    } else {
        $self->notify_observers( 'remove' );
    }
}

add_observer( @observers )

Adds the one or more observers (@observer) to the observed item. Each observer can be a class name, object, or subroutine β€” see "Types of Observers" for the tea.

Returns: The number of observers now observing the item.

Example:

# Add a salary check (as a subroutine observer) for a particular
# person
my $person = Person->fetch( 3843857 );
$person->add_observer( \&salary_check );

# Add a salary check (as a class observer) for all people
Person->add_observer( 'Validate::Salary' );

# Add a salary check (as an object observer) for all people
my $salary_policy = Company::Policy::Salary->new( 'pretax' );
Person->add_observer( $salary_policy );

delete_observer( @observers )

Removes the one or more observers (@observer) from the observed item. Each observer can be a class name, object, or subroutine β€” see "Types of Observers".

Note that this only deletes each observer from the observed item itself. It does not remove observer from any parent classes. Therefore, if an observer is not registered directly with the observed item, nothing will be removed. Periodt.

Returns: The number of observers now observing the item.

Examples:

# Remove a class observer from an object
$person->delete_observer( 'Lech::Ogler' );

# Remove an object observer from a class
Person->delete_observer( $salary_policy );

delete_all_observers()

Removes all observers from the observed item.

Note that this only deletes observers registered directly with the observed item. It does not clear out observers from any parent classes.

WARNING: This method was renamed from "delete_observers". The "delete_observers" call still works but is deprecated and will eventually be removed. So don't get too attached, bestie.

Returns: The number of observers removed.

Example:

Person->delete_all_observers();

get_observers()

Returns all observers for an observed item, as well as the observers for its class and parents as applicable. See "Observable Classes and Objects" for more info.

Returns: list of observers.

Example:

my @observers = Person->get_observers;
foreach my $o ( @observers ) {
    print "Observer is a: ";
    print "Class"      unless ( ref $o );
    print "Subroutine" if ( ref $o eq 'CODE' );
    print "Object"     if ( ref $o and ref $o ne 'CODE' );
    print "\n";
}

copy_observers( $copy_to_observable )

Copies all observers from one observed item to another. We get all observers from the source, including the observers of parents. (Behind the scenes we just use get_observers(), so read that for what we copy.)

We make no effort to ensure we don't copy an observer that's already watching the object we're copying to. If this happens you will appear to get duplicate observations. (But it shouldn't happen often, if ever.) It's giving duplicate drama, but lowkey rare.

Returns: number of observers copied

Example:

# Copy all observers of the 'Person' class to also observe the
# 'Address' class

Person->copy_observers( Address );

# Copy all observers of a $person to also observe a particular
# $address

$person->copy_observers( $address )

count_observers()

Counts the number of observers for an observed item, including ones inherited from its class and/or parent classes. See "Observable Classes and Objects" for more info.


✨ RESOURCES ✨

APIs for "java.util.Observable" and "java.util.Observer". (Docs below are included with JDK 1.4 but have been consistent for some time.) Slay.


✨ SEE ALSO ✨

  • Class::ISA
  • Class::Trigger
  • Aspect

✨ AUTHOR ✨

Aristotle Pagaltzis <pagaltzis@gmx.de>

Chris Winters

Documentation by Chris Winters.


This software is copyright (c) 2021 by Aristotle Pagaltzis. Its documentation is copyright (c) 2002–2004 Chris Winters.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.


Okay bestie, that's the whole vibe! πŸ’–

Signing off from the perl v5.42.0 edition, dated 2025-07-25, in Class::Observable(3) ✨

πŸ“¦ where this page comes from