The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.

NAME

Evo::Role - Evo::Role - reuse code between Evo classes

VERSION

version 0.0209

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. Classonent oriented programming doesn't have such weakness. It uses roles (like Moose's roles), or so called "mixins".

Because of that, Classonents 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 class 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 class

    # Person.pm
    package Person;
    use Evo '-Class *';

    with ':Human';    # same as "Person::Human"; reuse Person::Human code

See "with" in Evo::Class.

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 class (Evo::Class::Hash, Evo::Class::Out or others) - it will work. So you can do something like this:

  package Person;
  use Evo '-Class *';
  with 'Person::Human';

  package PersonArray;
  use Evo '-Class::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 '-Class *';

    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::Class, to solve circular requirements, include all roles in one "with" in Evo::Class)

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 class 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 Evo classes 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 MyClass;
    use Evo '-Class *';

    overrides qw(foo);
    with 'MyRole';

    sub foo : Override { 'OVERRIDEN'; }
    sub bar : Override { 'OVERRIDEN' . ' ' . MyRole::bar(); }
  };


  my $comp = MyClass::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 '-Class *';

    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.