na wstępie – ja wiem, że perl nie jest prawdziwie obiektowy. nie ma enkapsulacji. jeśli szukasz tutaj okazji do zwady to jej nie znajdziesz. wiem, że java, smalltalk, python, ruby i pewnie z 3 tony innych języków mają lepszą obiektowość.
nie musisz tego dalej czytać. to niebezpieczne. zawiera kawalki kodu perlowego, które (co dowiedli angielscy naukowcy) są niebezpieczne i powinny być traktowane tak jak niebezpieczne odpady z elektrowni jądrowych.
papa.
ok. teraz, odstraszywszy cieniasów można przejść do sedna.
programowanie obiektowe w perlu jakie jest każdy koń widzi. jest kilka metod, prostych, skomplikowanych, fajnych i totalnie niefajnych.
najprostszą metodą na zrobienie obiektowej klasy jest coś takiego:
package costam;<br /> use strict;<br /> sub new {<br /> my $class = shift;<br /> my $self = {};<br /> bless $self, $class;<br /> return $self;<br /> }
i potem w każdej funkcji (która teraz jest metodą) piszemy na początku:
my $self = shift;
i już. dane obiektu trzymamy w $self, traktując go jako referencję na hasza. proste, miłe i skuteczne.
część mądrych ludzi stwierdziła, że jest to niebezpieczne bo z zewnątrz każdy może zobaczyć jakie zmienne (wartości w haszu) ma obiekt zadeklarowane. stąd pojawiła się idea obiektów inside-out, które są bezpieczne (poniekąd), modne i ogóle fajne. i których ni chu-chu nie lubię i polubić chyba nie zdołam.
trzymam się za to obiektów bazujących na haszach.
pisanie za każdym razem my $self = shift; jest upierdliwe.
brak akcesorów do zmiennych bywa niemiły.
konieczność pisania za każdym razem praktycznie tego samego sub new {} jest wkurzająca.
czy nie da się z tym nic zrobić?
da! na każdy problem jest lekarstwo (w przypadku perla jest to wiele lekarstw, gdzie każdy ma ulubione inne).
moim ulubionym lekarstwem jest spiffy. miły moduł, którego użycie wygląda tak:
use Spiffy qw( -Base );
i to wszystko.
co daje?
no cóż. zacznijmy od przepisanie pierwszego przykładu (klasa co nic nie robi i ma tylko konstruktor). wersja spiffy:
package costam;<br /> use strict;<br /> use Spiffy qw( -Base );
i to wszystko. konstruktor jest tworzony automatycznie. co jeszcze?
otóż pojawia się kilka fajnych rzeczy:
- upierdliwe my $self = shift; znika – nie trzeba go pisać. spiffy dodaje to automatycznie do kodu w czasie kompilacji. niby pierdoła, ale jak ma się moduł z np. 40 metodami, to to już jest 40 linijek zbędnego kodu mniej!
- możliwość definiowania pól (z automatycznym tworzeniem akcesorów):
field ‘logfile' => undef;
zdefiniuje metodę $self->logfile która w zależności czy zostanie wykonana bez czy z parametrami to odpowiednio zwróci albo ustawi wartość pola ‘logfile' (podawanie wartości domyślnej => jest opcjonalne) - pola stałe:
const ‘debuglevel' => 3;
zasadniczo generuje metodę debuglevel która zwraca wartość 3 - stub'y – metody które istnieją, ale próba wywołania skończy się błędem. służą do wymuszania przedefiniowania określonych metod w klasach dziedziczących:
stub ‘save';
musi zostać przedefiniowany, inaczej każde wywołanie $object->save zakończy się błędem (informującym o przyczynie, a nie czymś w stylu “undefined subroutine"). - uproszczone wołanie metod z klas dziedziczonych. bez spiffy'ego trzeba było robić coś w stylu $self->SUPER::metoda(@_); teraz wystarcza proste:
super; - syntactic sugar – w modułach które używają spiffy'ego nie trzeba podawać już tego “1;" na końcu.
- wszystkie zdefiniowane pola są honorowane przy wywołaniu konstruktora. tak więc można:
package cos;
use strict;
use Spiffy qw( -Base );
field ‘x' => 0;
field ‘y' => 0;
package main;
my $object = cos->new( x=> 10 );
i zadziała to w pełni logicznie 🙂
oczywiście nie ma róży bez kolców.
ponieważ dodanie kilku z powyższych punktów wymusza używania czarnej magii (filtry kodu), spiffy nie jest do końca zgodny ze wszystkim co chodzi po ziemi.
pojawiły się nawet opinie, że jakieś moduły są psute używaniem spiffy'ego – czego osobiście przez kilka lat spiffy'owania nie zauważyłem, ale nie twierdzę, że problem nie istnieje.
istotnym problemem jest coś takiego.
bez spiffy'ego można napisać klasę dziedziczącą z kilku klas tak:
use base qw( klasa::a klasa::b );
ale jeśli używamy spiffy'ego to sprawa się komplikuje i trzeba to rozbić:
use base qw( klasa::a );<br /> use base qw( klasa::b );
nie jest to w/g mnie wielkie utrudnienie – zwłaszcza biorąc pod uwagę ile rzeczy spiffy mi zaoszczędza.
na koniec drobna informacja o innych rzeczach jakie spiffy daje – z tym, że są to rzeczy z których zdecydowanie rzadziej korzystam:
- prywatne metody. metoda zapisana poprzez:
my sub jakas_metoda {
staje się metodą prywatną i może zostać wywołana jedynie przez inne metody tego obiektu - dopisanie XXX pozwala na proste debugowanie kodu – poprzez wypisywanie ładnie sformatowanych (yaml) sturktur danych. przykład użycia z manuala:
XXX my @stuff = grep { /keen/ } $self->find($a, $b);
tak to wygląda. dzięki temu jak to działa można napisać np. coś takiego:
package modulik;<br /> use strict;<br /> use Spiffy qw( -Base );<br /> field 'x' => 0;<br /> field 'y' => 0;<br /> const 'wsp_x' => 1.33;<br /> sub suma {<br /> return ( $self->x + $self->y ) * $self->wsp_x;<br /> }<br /> package main;<br /> use strict;<br /> my $object = modulik->new(x => 10, y => 12);<br /> print $object->suma, "\n";
nie jest to może najbardziej zaawansowany kod jaki można sobie wyobrazić. ale spróbujcie go zapisać bez spiffy'ego.
aha. no i spiffy, wewnętrzenie trzyma wszyskie pola (field) jako klucze w $self – czyli tak jak perl przykazał.