package mixin::with;

use strict;
use warnings;
no strict 'refs';
use vars qw($VERSION);
$VERSION = '0.09';

=head1 NAME

mixin::with - declaring a mix-in class

=head1 SYNOPSIS

    package Dog::Retriever;
    use mixin::with 'Dog';


=head1 DESCRIPTION

mixin::with is used to declare mix-in classes.


=head2 When to use a mixin?

Mixin classes useful for those that I<add new functionality> to an
existing class.  If you find yourself doing:

    package Foo::ExtraStuff;
    use base 'Foo';
    sub new_method { ... }

    package Bar;
    use base qw(Foo Foo::ExtraStuff);

it's a good indication that Foo::ExtraStuff might do better as a mixin.

Instead of mixins, please consider using traits.  See L<Class::Trait> for an implementaiton.


=head2 How?

Basic usage is simple:

    package Foo::Extra;
    use mixin::with 'Foo';

    sub new_thing {
        my($self) = shift;
        ...normal method...
    }

C<use mixin::with 'Foo'> is I<similar> to subclassing from 'Foo'.

All public methods of Foo::Extra will be mixed in.  mixin::with
considers all methods that don't start with an '_' as public.


=head2 Limitations of mixins

There's one critical difference between a normal subclass and one
intended to be mixin.  It can have no private methods.  Instead, use lexical methods.

  my $private = sub { ... };
  $self->$private(@args);

instead of

  sub _private { ... }
  $self->_private(@args);

Don't worry, it's the same thing.


=cut

my %Mixers = ();
my $Tmp_Counter = 0;
sub import {
    my($class, $mixed_with) = @_;
    my $mixin = caller;

    my $tmp_pkg = __PACKAGE__.'::tmp'.$Tmp_Counter++;
    $Mixers{$mixin} = { mixed_with => $mixed_with,
                        tmp_pkg    => $tmp_pkg,
                      };

    require base;

    eval sprintf q{
        package %s;
        base->import($mixed_with);
    }, $mixin;

    return 1;
}


sub __mixers {
    my($class, $mixin) = @_;

    return @{$Mixers{$mixin}}{'mixed_with', 'tmp_pkg'};
}


=head1 FAQ

=over 4

=item What if I want to mixin with anything?

Sometimes a mixin does not care what it mixes in with.  Consider a
logging or error handling mixin.  For these, simply mixin with
UNIVERSAL.

    package My::Errors;
    use mixin::with qw(UNIVERSAL);


=item Why do I have to declare what I mixin with?

Two reasons.  One is technical, it allows C<SUPER> to work.

The other is organizational.  It is rare that a mixin is intended to be mixed with any old class.  It often uses methods as if it were a subclass.  For this reason it is good that it declares this relationship explicitly else the mixee won't be aware of the mixin's expectations.


=item Why use mixins instead of traits?

Good question.  Traits are definately a better idea then mixins, but mixins have two advantages.  They're simpler to explain, acting like a gateway drug to traits by introducing the concept of OO reuse by class composition rather than inheritance.

The other is mixins work more like a drop-in replacement for multiple inheritance.  In a large, hairy hierarchy mixins can often be used to trim the inheritance bush and make sense of things with a minimum of modification to the code.  Once this basic repair is done, the work of converting to traits can begin.

If these advantages don't apply, proceed directly to traits.

=back

=head1 AUTHOR

Current ( ~ 2024 ) maintainer: Shlomi Fish ( L<https://www.shlomifish.org/me/contact-me/> ).

Original author/maintainer:

Michael G Schwern <schwern@pobox.com>

=head1 LICENSE

Copyright 2002-2010 by Michael G Schwern

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

L<http://dev.perl.org/licenses/>

Shlomi Fish disclaims all copyright ownership to his changes.

=head1 SEE ALSO

L<mixin>, L<ruby> from which I stole this idea.

=cut

1;

