NAME

Class::Mixer - Arrange class hierarchy based on dependency rules

SYNOPSIS

   package Base;
   use Class::Mixer;
   sub x { 'Base::x' }

   package Main;
   use Class::Mixer before => 'Base';
   sub x { 'Main::x' }

   my $obj = Main->new();
   print "@Main::ISA\n";  # prints "Base Class::Mixer"

   package Mixin;
   use Class::Mixer before => 'Base', after => 'Main';
   sub x { 'Mixin::x' }

   package NewMain; 
   use Class::Mixer isa => 'Main', requires => 'Mixin';
   sub x { 'NewMain::x' }

   my $obj = NewMain->new();
   print "@NewMain::ISA\n";  # prints "Main Mixin Base Class::Mixer"

DESCRIPTION

This module is designed to solve a problem which occurs when using inheritance to mixin behaviors into a class hierarchy. The dependencies between a large number of mixin modules may be complex, making it tricky to get the base classes to inherit in the right order.

Then if you have class Main which gets the inheritance right, and you want to add a class Mixin which needs to go in the middle of the inheritance, you cannot simply do package NewMain; use base qw(Main Mixin); because Mixin will be put at the end of the inheritance chain.

Also, if you have a class Foo::Better which enhances the Foo behavior, the same problem occurs trying to mixin Foo::Better. And it is even worse if some classes have done use base 'Foo'; to try to enforce the correct hierarchy.

This module solves the problems by implementing a dependency-based hierarchy. You declare the relations between the classes, and an order of inheritance which will support those relations is determined automatically.

It combines functions from base and Class::C3 to do its job. It will require the given classes (unless optional) similar to use base. And it attempts to force c3 semantices, so you should do $self->method::next instead of SUPER for inheritance.

Inheritance rules

When you design your classes, instead of doing use base or our @ISA, do something like the following:

package Example;
use Class::Mixer before => 'BaseClass', 'OtherClass',
                 after => 'SoAndSoClass',
                 isa => 'PreviousExample',
                 requires => 'OtherBehavior',
                 optional => 'OtherClass';

The Class::Mixer class provides a basic new() method, which will call init(), which your classes can override.

The actual inheritance is computed for a class the first time new() is called.

The inheritance rules are described here:

before

The 'before' rule means this package must occur before some other class in the method dispatch order, which means that if they both define the same method, this class will be invoked before the other. In the inheritance hierarchy, this class should be a descendant of the other.

This is essentially the same as what you would get if you did use base instead. Which you can, by the way.

If no rule type is given, e.g. use Class::Mixer 'BaseClass'; then the before rule is assumed.

after

The 'after' rule means this package must occur after some other class in the method dispatch order. The other class will usually be a descendant of this one. This is best if used rarely, but it is nice when it is necessary.

It is essentially the same as doing before => 'this', optional => 'this' in the other class.

isa

The 'isa' rule establishes a very strong isa relationship between classes. All the classes are related by @ISA, of course, but this 'isa' means that the particular behaviors implements by the classes are the same, and that this one enhances the other.

The classes are kept together as close as possible in the computed hierarchy, and all rules which were applied to the other class will be applied to this class as well.

requires

The 'requires' rule says that this class needs the behavior from some other class, but the inheritance order is not important. Usually this is because the two classes do not share any methods.

optional

The 'optional' rule doesn't affect the class hierarchy. It simply makes the class not complain if the other class is not there.

This is useful when we want to say, "I do not really need this other class, but IF someone else does, I should be before (or after) the other class."

BUGS and TODO

Probably.

"optional" isn't fully implemented yet.

TODO?: implement a 'nota' rule, to prevent someone from putting this class in the same hierarchy as another.

Why not Traits?

Traits are complementary to inheritance, and do not address situations where one module needs to extend another by extending/wrapping/inheriting the same method. Traits are more concerned with properly adding new methods to a class, while Class::Mixer tries to solve some problems with complex mixin-style inheritance trees.

For example, I have a write method, and several optional behaviors which happen when write is called, such as an Index behavior and an VersionControl behavior. These will both override the write method and call the parent method before or after they do what they need to do. But this violates the flattening property of traits. The write method in Index and VersionControl are not conflicting; they need to inherit, perhaps in a specific order, so that both with be called.

It may be that what I am actually describing is event-based, because write is the event, and the behaviors are various things which need to happen when that event occurs. None of the event systems I have looked at have contraints to allow ordering the behaviors relative to each other, so even if I setup an event model, I would still need the solution which Class::Mixer provides.

SEE ALSO

Class::C3

base

The snide comments in mixin probably apply to this module as well.

AUTHOR

John Williams, <smailliw@gmail.com>

COPYRIGHT AND LICENCE

Copyright (c) 2009 by John Williams

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