Moose

Z Wikiverzity
Skočit na navigaci Skočit na vyhledávání

Moose je objektový systém, vytvořený jako knihovna v Perlu na knihovnou Class::MOP. V podstatě vytváří určitý způsob myšlení (školu, anticipující Perl6), jak zacházet s objekty v Perlu (s objekty se zde jinak dá zacházet celkem volně).

Info[editovat]

Související články[editovat]

  • KiokuDB – persistentní objekty založené na Moose jsou transparentně ukládány pomocí různých databázových backendů

Externí odkazy[editovat]

Instalace[editovat]

Ubuntu[editovat]

Při výběru balíku libmoose-perl budou nainstalovány i následující balíky:

  • libb-hooks-endofscope-perl (verze 0.08-1) bude nainstalován
  • libclass-load-xs-perl (verze 0.03-1) bude nainstalován
  • libdevel-globaldestruction-perl (verze 0.04-2) bude nainstalován
  • libdevel-partialdump-perl (verze 0.15-1) bude nainstalován
  • libeval-closure-perl (verze 0.06-1) bude nainstalován
  • libmoose-perl (verze 2.0401-1) bude nainstalován
  • libnamespace-clean-perl (verze 0.22-1) bude nainstalován
  • libsub-exporter-perl (verze 0.982-1) bude nainstalován
  • libsub-identify-perl (verze 0.04-1build2) bude nainstalován
  • libtask-weaken-perl (verze 1.03-1) bude nainstalován
  • libvariable-magic-perl (verze 0.47-1build1) bude nainstalován


Tutoriál[editovat]

Sytému Moose je věnována kapitola Objects v knize Modern Perl#07. Objekty. Touto kapitolou je inspirován tento tutoriál. Poznamenejme, že direktivami

use utf8;
binmode STDOUT, ':utf8';

můžeme (pro legraci) používat i české názvy proměnných i s diakritikou (pro vážné programování bych to ale asi nedoporučoval).

Z nějakých důvodů to ale s diakritikou nefunguje v případě, že tak nazveme třídu; příkaz

package Kočka

vyvolá chybu:

Kočka is not a module name at /usr/lib/perl5/Class/MOP/Package.pm line 207.
BEGIN failed--compilation aborted at ./07.01Kocka.pl line 9.

Nejspíš se jedná o bug – viz Pragmas > utf8 až na konci: Bugs.

01. Konstruktory – stvoření koček[editovat]

Tak tedy, vytvoříme si třídu koček, které umí mňoukat, a dvě její instance, Lízu a Mňauka, které pak z mňoukání vyzkoušíme. Ale ještě k terminologii, která je v objektovém programování celkem obvyklá:

  • Třída je v Perlu v podstatě to samé, co balík. Má to logiku, zpravidla definice tříd jsou i v jiném (např. knihovním) souboru *.pm, ale klidně mohou být i v tom samém souboru. Hlavně to ale znamená, že všechny proměnné tam jsou plně kvalifikované, tj. mají vlastní jmenný prostor, daný názvem třídy.
  • Konkrétní instance se vytvářejí pomocí konstruktoru new
  • metoda je funkce, asociovaná s třídou. Když se volá, má vždy nějaký invokant, se kterým metoda pracuje:
    • když vytváříme nový objekt, je tím invokantem jméno třídy
    • když voláme metodu u konkrétní instance, je tím invokantem tato instance.
  • Invokant se v Perlu předává funkci jakožto první argument.

I když se nejedná o nějaký předpis jazyka Perl, jde o určitou konvenci, že jména tříd začínají velkými písmeny (třída je jakoby větší, protože může mít mnoho instatncí), zatímco jména instancí začínají malými písmeny, tedy je: Kocka líza. (Je to přesně naopak než v češtině, angličtině i jiných jazycích, kde píšeme kočka Líza.) Pokud se nám to nelíbí, nemusíme samozřejmě tuto konvenci dodržovat.

 1 #! /usr/bin/perl
 2 
 3 use Modern::Perl;
 4 use utf8;
 5 binmode STDOUT, ':utf8';
 6 
 7 {
 8     package Kocka;
 9     use Moose;
10 
11     sub mňau
12     {
13         my $self = shift;
14         say 'Mňau!';
15     }
16 }
17 
18 my $líza = Kocka->new();
19 my $mňouk = Kocka->new();
20 
21 $líza->mňau();
22 $mňouk->mňau();
  • 1. Říkáme shellu, který program má interpretovat tento skript.
  • 2. Programujeme ve stylu Modern::Perl.
  • 4. Co je ve skriptu, bude bráno jako znaky UTF-8
  • 5. Co půjde na standardní výstup, bude chápáno také jako text v UTF-8
  • 7. Začneme nový blok (složená závorka), protože to bude nový balík s vlastním jmenným prostorem.
  • 8. Vytvářím třídu Kocka.
  • 9. Používám k tomu knihovnu Moose. (Tím je dané např. i implicitní vytvoření konstruktoru new.)
  • 11. Podprogram, přesněji řečeno metoda mňau, kde říkám, jak má libovolná kočka mňoukat.
  • 13. Uložíme invokant (tedy první parametr funkce) do proměnné $self.
  • 14. Tady konečně říkám, jak má kočka mňoukat.
  • 18.–19. Postupně vytvářím dvě instance koček, tedy dva konkrétní případy, kočky Lízu a Mňouka.
  • 21.-22. Vyzkouším si, jak mňoukají.

Výstup skriptu bude jednoduchý:

Mňau!
Mňau!

Někdo namítne, že je to nedokonalé, že Mňouk je kocour a ten že mňouká jinak. Námitka je na místě, časem zjistíme, že je možno vytvářet i různá pohlaví, která budou mňoukat jinak.

02. Atributy[editovat]

Když už vytváříme takovéto kočky, bylo by dobré, aby samy kočky věděly, jak se jmenují a mohly se nám na požádání třeba představit. Tedy všechny kočky (tj. třída koček) by měla mít nějaký šuplíček, kam by se ukládalo jméno té které kočky. A pak třeba její věk (kolik je jí let) a jestli třeba nemá nějakou dietu (třeba vytváříme databázi pacientů v kočičí nemocnici).

Tyto tzv. atributy (někdy se jim říká stav anebo prostě data dané instance) se v Mose přikládají pomocí funkce has(); zde je možné rovněž určit, jestli je možné tento atribut dále měnit a jaký typ proměnné bude použit, což představuje určitý typ kontroly nad tím, jak budeme moci s objektem nakládat.

 1 #! /usr/bin/perl
 2 
 3 use Modern::Perl;
 4 use utf8;
 5 binmode STDOUT, ':utf8';
 6 
 7 {
 8     package Kocka;
 9     use Moose;
10 
11     has 'jméno', is=>'ro', isa=>'Str';
12     has 'věk',   is=>'rw', isa=>'Int';
13     has 'dieta', is=>'rw';
14 }
15 
16 my $tlusťoch = Kocka->new(jméno=>'Tlusťoch', věk=8, dieta=>'produkty moře');
17 
18 say $tlusťoch->jméno(), ', ', $tlusťoch->věk(), ' let, dieta: ', $tlusťoch->dieta();
19 
20 $tlusťoch->dieta( 'neslaná');
21 say $tlusťoch->jméno(), ', ', $tlusťoch->věk(), ' let, nová dieta: ', $tlusťoch->dieta();
  • 11. atribut jméno je read-only a je to string
  • 12. atribut věk je read/write a je to číslo
  • 13. atribut dieta je read/write a není řečeno, jaký typ proměnné to bude
  • 16. vytvoříme tlusťocha, který se jmenuje Tlusťoch, je mu 8 let a má speciální dietu
  • 18. vypíšeme výše zadané informace o tlusťochovi a to pomocí samočinně vytvořených metod (kterým se říká akcesory), které se volají bez argumentů (prázdné závorky)
  • 20. změníme mu dietu pomocí metody dieta, která se automaticky vytvořila právě z účelem změny atributu; formálně je shodná s akcesorem, akorát že se volá s příslušným argumentem; takové metodě se pak říká mutátor
  • 21. znovu vypíšeme status

Výstup programu:

Tlusťoch, 8 let, dieta: produkty moře
Tlusťoch, 8 let, nová dieta: neslaná

03. Enkapsulace[editovat]

Enkapsulace neboli zapouzdření znamená, že se skryjí vnitřní detaily objektu.

Příklad: Namísto toho, abychom zadávali věk kočky, zadáme datum jejího narození a její věk se pak automaticky spočítá. Pozměníme třídu Kocka:

{
    package Kocka;
    use Moose;

    has 'jméno', is=>'ro', isa=>'Str';
    has 'dieta', is=>'rw';
    has 'rok_narození',  is=>'ro', isa=>'Int';

    sub věk {
        my $self = shift;
        my $rok = (localtime)[5] + 1900;
        return $rok - $self->rok_narození;
    }
}

Pomineme, že věk zde počítáme jen přibližně (zanedbáváme datum narození), ale co je důležité:

  1. změnil se popis třídy: namísto atributu věk je zde uložen atribut rok narození a je definována metoda na výpočet věku
  2. způsob volání se přitom ale nezměnil – věk dané kočky zjistíme stejně jako předtím:
say $tlusťoch->věk();

Tak to je ta enkapsulace: Zatímco se nějaký vnitřní mechanismus třídy změnil, zvenku se to chová stále stejně; při volání metody nás jakoby nemusí zajímat, jak je to uvnitř té třídy zařízeno, že to tak funguje, hlavně, že to funguje.

Další výhoda takového přístupu je, že si můžeme definovat nějaké defaultní hodnoty, které konstruktor objektu použije v případě, že nějaké atributy nejsou zadány explicitně; například předpokládáme, že zakládáme rodný list kočky a tím pádem můžeme předpokládat, že pokud nezadáme její rok narození, bude se jednat o nynější rok; pak defaultní hodnotu tohoto atributu definujeme např. následovně:

    has 'rok_narození',  is=>'ro', isa=>'Int',
                         default => sub{(localtime)[5] + 1900};