NAME

Minions - What is your API?

SYNOPSIS

# Imagine a counter used like this:

use Test::Most tests => 5;
use Example::Synopsis::Counter;

my $counter = Example::Synopsis::Counter->new;

is $counter->next => 0;
is $counter->next => 1;
is $counter->next => 2;

throws_ok { $counter->new } qr/Can't locate object method "new"/;

throws_ok { Example::Synopsis::Counter->next }
          qr/Can't locate object method "next" via package "Example::Synopsis::Counter"/;


# The counter class:

package Example::Synopsis::Counter;

use Minions
    interface => [ qw( next ) ],

    implementation => 'Example::Synopsis::Acme::Counter';

1;


# And the implementation for this class:

package Example::Synopsis::Acme::Counter;

use Minions::Implementation
    has => {
        count => { default => 0 },
    }
;

sub next {
    my ($self) = @_;

    $self->{$__count}++;
}

1;

STATUS

This is an early release available for testing and feedback and as such is subject to change.

DESCRIPTION

Minions is a class builder that makes it easy to create classes that are modular, which means there is a clear separation between what end users need to know (the interface for using the class) and implementation details that users don't need to know about.

Classes are built from a specification that declares the interface of the class (i.e. what commands minions of the classs respond to), as well as a package that provide the implementation of these commands.

This separation of interface from implementation details is an important aspect of modular design, as it enables modules to be interchangeable (so long as they have the same interface).

It is not a coincidence that the Object Oriented concept as originally envisioned was mainly concerned with messaging, where in the words of Alan Kay (who coined the term "Object Oriented Programming") objects are "like biological cells and/or individual computers on a network, only able to communicate with messages" and "OOP to me means only messaging, local retention and protection and hiding of state-process, and extreme late-binding of all things." (see The Deep Insights of Alan Kay).

RATIONALE

Due to Perl's "assembly required" approach to OOP, there are many CPAN modules that exist to automate this assembly, perhaps the most popular being the Moose family. Moo(se) is very effective at simplifying class building but this is typically achieved at the expense of Encapsulation (the hiding of implementation details from end users).

To see this first hand, try writing the fixed size queue from "OBJECT COMPOSITION" in Minions::Implementation using Moo, bearing in mind that the only operations the queue should allow are push, pop and size. It is also a revealing exercise to consider how this queue would be written in Ruby or PHP (e.g. would you need to expose all object attributes via methods?).

Minions takes inspriation from Moose's declaratve approach to simplifying OO automation, but does not require or encourage encapsulation to be sacrificed.

The Tale of Minions

There once was a farmer who had a flock of sheep. His typical workday looked like:

$farmer->move_flock($pasture)
$farmer->monitor_flock()
$farmer->move_flock($home)

$farmer->other_important_work()

In order to devote more time to other_important_work(), the farmer decided to hire a minion, so the work was now split like this:

$shepherd_boy->move_flock($pasture)
$shepherd_boy->monitor_flock()
$shepherd_boy->move_flock($home)

$farmer->other_important_work()

This did give the farmer more time for other_important_work(), but unfornately $shepherd_boy had a tendency to cry wolf so the farmer had to replace him:

$sheep_dog->move_flock($pasture)
$sheep_dog->monitor_flock()
$sheep_dog->move_flock($home)

$farmer->other_important_work()

$sheep_dog was more reliable and demanded less pay than $shepherd_boy, so this was a win for the farmer.

Object Oriented design is essentially the act of minionization, i.e. deciding which minions ($objects) will do what work, and how to communicate with them (using an interface).

USAGE

Via Import

A class can be defined when importing Minions e.g.

package Foo;

use Minions
    interface => [ qw( list of methods ) ],

    construct_with => {
        arg_name => {
            assert => {
                desc => sub {
                    # return true if arg is valid
                    # or false otherwise
                }
            },
            optional => $boolean,
        },
        # ... other args
    },

    implementation => 'An::Implementation::Package',
    ;
1;

Minions->minionize([HASHREF])

A class can also be defined by calling the minionize() class method, with an optional hashref that specifies the class.

If the hashref is not given, the specification is read from a package variable named %__meta__ in the package from which minionize() was called.

The class defined in the SYNOPSIS could also be defined like this

use Test::More tests => 2;
use Minions ();

my %Class = (
    name => 'Counter',
    interface => [qw( next )],
    implementation => {
        methods => {
            next => sub {
                my ($self) = @_;

                $self->{-count}++;
            }
        },
        has  => {
            count => { default => 0 },
        },
    },
);

Minions->minionize(\%Class);
my $counter = Counter->new;

is $counter->next => 0;
is $counter->next => 1;

Specification

The meaning of the keys in the specification hash are described next.

interface => ARRAYREF

A reference to an array containing the messages that minions belonging to this class should respond to. An exception is raised if this is empty or missing.

The messages named in this array must have corresponding subroutine definitions in a declared implementation, otherwise an exception is raised.

construct_with => HASHREF

An optional reference to a hash whose keys are the names of keyword parameters that are passed to the default constructor.

The values these keys are mapped to are themselves hash refs which can have the following keys.

See Minions::Manual::Construction for more about construction.

optional => BOOLEAN (Default: false)

If this is set to a true value, then the corresponding key/value pair need not be passed to the constructor.

assert => HASHREF

A hash that maps a description to a unary predicate (i.e. a sub ref that takes one value and returns true or false). The default constructor will call these predicates to validate the parameters passed to it.

implementation => STRING | HASHREF

The name of a package that defines the subroutines declared in the interface.

Alternatively an implementation can be hashref as shown in the synopsis above.

Minions::Implementation describes how implementations are configured.

Bindings

The implementation of a class can be quite easily changed from user code e.g. after

use Minions
    bind => { 
        'Foo' => 'Foo::Fake', 
        'Bar' => 'Bar::Fake', 
    };
use Foo;
use Bar;

Foo and bar will be bound to fake implementations (e.g. to aid with testing), instead of the implementations defined in their respective modules.

Introspection

Behavioural and Role introspection are possible using $object->can and $object->DOES which if called with no argument will return a list (or array ref depending on context) of methods or roles respectiively supported by the object.

See the section "Using multiple roles" from "EXAMPLES" in Minions::Role for an example.

Also note that for any class Foo created using Minions, and for any object created with Foo's constructor, the following will always return a true value

$object->DOES('Foo')

BUGS

Please report any bugs or feature requests via the GitHub web interface at https://github.com/arunbear/minions/issues.

AUTHOR

Arun Prasaad <arunbear@cpan.org>

COPYRIGHT

Copyright 2014- Arun Prasaad

LICENSE

This library is free software; you can redistribute it and/or modify it under the terms of the GNU public license, version 3.

SEE ALSO