NAME
Evo::Role - Evo::Role - reuse code between components
VERSION
version 0.0199
DESCRIPTION
OO is considered an anti-pattern because it's hard to change base class and reuse the code written by other person (Fragile base class problem), and every refactoring makes OO applications low-tested or extra-tested. Component oriented programming doesn't have such weakness. It uses roles (like Moose's roles), or so called "mixins".
Because of that, Components are faster, simpler, and more reusable Also Roles can protect you from method and attributes clashing, because all methods and attributes will be installed into one file
I'll write an article about this late (maybe)
Here is a breaf overview
Building and using component roles
To share method, add Role
tag. All attributes are shared automatically. In our case method upper_name
and attribute name
are provided by role.
# Person/Human.pm
package Person::Human;
use Evo '-Role *';
has 'name';
sub upper_name : Role { uc shift->name }
And to use it in the component
# Person.pm
package Person;
use Evo '-Comp *';
with ':Human'; # same as "Person::Human"; reuse Person::Human code
See "with" in Evo::Comp.
Shortcuts
Evo::Role
supports shortcuts, here :Human
in Person
is resolved to Person::Human
. This helps a lot during refactoring. See "shortcuts" in Evo for more information
Storage agnostic
The good news are roles don't care what type of storage will be used by derived component (Evo::Comp::Hash, Evo::Comp::Out or others) - it will work. So you can do something like this:
package Person;
use Evo '-Comp *';
with 'Person::Human';
package PersonArray;
use Evo '-Comp::Out *';
with 'Person::Human';
package main;
use Evo;
use Data::Dumper;
my $person_hash = Person::new(name => 'foo');
my $person_array = PersonArray::init([1, 2, 3], name => 'bar');
# hash
say Dumper $person_hash;
# array
say Dumper $person_array;
In the example above, Person
is based on hashes, while PersonArray
is based on arrays. They both use Person::Human
role.
Let's separate Person::Human
into 2 different roles;
# Person/Human.pm
package Person::Human;
use Evo '-Role *';
has 'name';
# Person/LoudHuman.pm
package Person::LoudHuman;
use Evo '-Role *';
requires 'name';
sub upper_name : Role { uc shift->name }
package Person;
use Evo '-Comp *';
with ':LoudHuman', ':Human';
sub intro { say "My name is ", shift->upper_name }
Person::LoudHuman
provides method upper_name
. requires 'name'
, which is used by upper_name
ensures that derivered class has this method or attribute. (attributes should be described before "with" in Evo::Comp, to solve circular requirements, include all roles in one "with" in Evo::Comp)
features
role_gen
Creates generator, very similar to "export_gen" in Evo::Export
role_proxy
role_proxy 'My::Another', 'My::Another2';
Proxy attributes and methods from one role to another
role_methods
:Role
attribute is preffered
requires
List method that should be available in component during role installation. If you require attribute, describe it before "with". If you have circular dependencies, load all roles in the single "with".
Overriding methods
It's possible to override method in the derived class. By default you're protected from method clashing. But you can override role methods with "overrides" in Evo::Role or Override
subroutine attribute. And because components are flat, you can easely acces role's methods (just like SUPER) - just use Role::Package::name
syntax.
package main;
use Evo;
{
package MyRole;
use Evo '-Role *; -Loaded';
has foo => 'FOO';
sub bar : Role : {'BAR'}
package MyComp;
use Evo '-Comp *';
overrides qw(foo);
with 'MyRole';
sub foo : Override { 'OVERRIDEN'; }
sub bar : Override { 'OVERRIDEN' . ' ' . MyRole::bar(); }
};
my $comp = MyComp::new();
say $comp->foo; # OVERRIDEN
say $comp->bar; # OVERRIDEN BAR
Many overriden methods is a signal for refactoring. But sometimes it's ok to provide a "default" method for testing, or override 3d party library
SYNOPSYS
package main;
use Evo;
{
# Evo::Load is only for one-file examples
package Person::Human;
use Evo '-Role *; -Loaded';
has 'name';
sub upper_name : Role { uc shift->name }
package Person;
use Evo '-Comp *';
with ':Human'; # same as "Person::Human"; reuse Person::Human code
sub intro { say "My name is ", shift->upper_name }
};
my $alex = Person::new(name => 'alex');
$alex->intro;
AUTHOR
alexbyk.com
COPYRIGHT AND LICENSE
This software is copyright (c) 2016 by alexbyk.
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.