|| | ||| okładka | intro | spis treści | redakcyjne | prenumerata | adv.
Magazyn Prawdziwych Internautów
numer 25:. aktualności | komputery | internet | kultura ||| || |

 

The road to better programming: rozdział 5
Teodor Zlatanov
Programowanie zorientowane obiektowo w Perl'u
(tłum. Michał 'Podles' Podlewski , gniewko_syn_rybaka@wp.pl)

Czym jest programowanie zorientowane obiektowo (w skrócie OOP, od Object Oriented Programming)?
OOP jest rodzajem programowania lub ogólniej podejściem do rozwiązywania problemów. Algorytmy, z drugiej strony, są konkretnymi podejściami do rozwiązywania konkretnych problemów. OOP jest z natury silnym sposobem, który dąży do stworzenia opartych na funkcjach i procedurach metod programowania mniej powiązanych z konkretnym problemem.
Ten artykuł obejmuje podstawy OOP w Perl'u, porównuje je z rozwiązaniami proceduralnymi i funkcjonalnymi i pokazuje, jak z niego korzystać w programach i modułach. Proszę jednak pamiętać, że jest to raczej streszczenie niż szczegółowe wyjaśnienie wszystkich aspektów OOP, ponieważ dogłębne przedstawienie tego zagadnienia zajęłoby raczej nie pojedyncze strony, ale całe książki, któ?e w dodatku ostały juz napisane. Po więcej informacji zajrzyj do bibliografii.

Co to dokładnie jest OOP?

Jest to technika rozwiązywania problemów przy użyciu obiektów. Obiekty w języku programowania to takie twory, których zachowanie i właściwości są niezbędne aby od ręki rozwiązać problem. Definicja powinna być bardziej konkretna, jednak nie może ze wzgledu na występującą obecnie niesłychaną różnorodność podejść do OOP w branży komputerowej.

W kontekście programowania w Perlu OOP nie jest niezbędne do używania języka. Perl w wersji 5 i wyższych zachęca do OOP, ale go nie wymaga. Wszystkie biblioteki Perla są modułami, co oznacza, że używają przynajmniej podstaw OOP. Co więcej, większość bibliotek Perla jest implementowana jako obiekty, co oznacza, że wykorzystując je uzytkownik musi stosować sie do ich związanego z OOP zachowania, własciwości i interfejsu.

Najważniejsze cechy języka programowania obiektowego.

Geneeralnie trzy cechy języka programowania są najważniejsze dla programowania obiektowego. Sa to dziedziczenie, polimorfizm i 'obudowanie' (encapsulation).

Perl obsułguje dziedziczenie. Występuje ono kiedy jeden obiekt (potomek) używa drugiego (rodzica) jako punktu startowego, a potem modyfikuje jego właściwośi i zachowania w razie potrzeby. Ta relacja potomek-rodzic jest najistotniejsza w OOP, jako że umożliwia tworzenie jednych obiektów z drugich. Właśnie to ponowne użycie obiektów czyni z OOP ulubioną metodę programistów.

Istnieją dwa rodzaje dziedziczenia: pojedyncze i wielokrotne. Pojedyncze dziedziczenie wymaga od obiektu-potomka posiadania tylko jednego rodzica, natomiast dziedziczenie wielokrotne jest bardziej liberalne (w programowaniu, tak jak w życiu posiadanie więcej niż dwojaga rodziców może uczynić dzieciństwo trudnym, więc nie należy nadużywać wielokrotnego dziedziczenia). Perl obsługuje dziedziczenie wielokrotne, jednak w praktyce rzadko spotyka się korzystanie z dwojga lub więcej rodziców.

Polimorfizm (z greckiego - wielokształtność) jest techniką polegającą na umożliwianiu obiektowi bycia widzianym jako inny obiekt. Jest to troche złożone, więc posłużę się przykładem. Powiedzmy, że masz farmę z czterema owcami (Ovis aries), ale kupiłeś właśnie dwie kozy (Capra hircus) i jednego owczarka niemieckiego (Canis lupis familiaris). Jak wiele zwierząt posiadasz? Dodajesz wszystkie owce, kozy i psa - wynik wynosi siedem. Własnie użyłeś polimorfizmu traktując trzy różne gatunki zwierząt jako jeden typ - 'zwierze' dla potrzeb liczenia.

W Perlu polimorfizm jest w pełni obsługiwany. Nie jest on jednak używany bardzo często, ponieważ programiści Perla z reguły wolą zmieniać odzedziczone zachowania raczej poprzez zmiane właściwości niż poprzez zmiane odziedziczonego zachowania. To oznacza, że częściej widzi się kod tworzący trzy obiekty 'IO::Socket::INET', jeden dla odbierania i transmisji pakietów UDP na porcie 234, jeden dla odbierania pakietów TCP na porcie 80 i jeden dla transmisji pakietów na porcie 1024, niż kod któ?y używa oddzielnie 'IO::Socket::INET::UDPTransceiver' dla pierwszego przypadku, 'IO::Socket::INET::TCPReceiver' dla drugiego i 'IO::Socket::TCPTransmitter' dla trzeciego.

Puryści OOP uważają, że wszystko powinno być właściwie sklasyfikowane, jednak programisci Perla nie są pod żadnym względem purystami. Traktują zasady OOP z dystansem, dzięki czemu sa na przyjęciach żródłem lepszej zabawy niż puryści OOP.

Obudowanie (encapsulation) odnosi się do takiego definiowania zachowania i własciwości obiektu aby uczynić go niedostępnym dla użytkowników, dopóki autor obiektu nie dopuści do takiego dostępu. W ten sposób użytkownicy obiektów nie mogą robić tego, czego robić nie powinni i nie mają dostępu do danych, do których nie powinni mieć dostępu. Perl pozwala na obudowanie (encapsulation) w zwykły, luźny sposób.

Dlaczego OOP jest silną techniką?

Wracając do głównego tematu - dlaczego OOP jest silną techniką możemy zobaczyć, że OOP zawiera kilka kluczowych koncepcji trudnych do połączenia z programowanie funkcjonalnym (PF) i programowaniem proceduralnym (PP). Po pierwsze, PP ani PF nie znają koncepcji dziedziczenia ani polimorfizmu klas, ponieważ nie znają samych klas. Obudowanie (encapsulation) występuje w PP i PF, ale tylko na poziomie procedury a nigdy jako atrybut klasy czy obiektu. Dlatego własnie programiści wolą przejść całkiem na OOP niż łączyć ze sobą niekompatybilne elementy.

Niektórzy udowadniają, że wszystkie programy są ostatecznie redukowane do proceduralnego wykonywania instrukcji, więc każdy program OOP, niezależnie jak 'czysto' jest ono zaimplementowane, zawiera 'proceduralny' kod w swoich funkcjach (zwanych metodami) i w kodzie tworzącym pierwszy obiekt, wykonujący resztę zadań. Nawet w języku tak bliskim czystemu OOP jak Java nie można uniknąć użycia funkcji "main()". Dlatego wydawałoby się, że OOP jest tylko podzbiorem PP. Jednak taka redukcja OOP nie powinna obchodzić programisty czy inżyniera oprogramowania bardziej niż instrukcje assemblera wykonywane dla każdej operacji. Pamiętaj, że programowanie obiektowe to tylko sposób tworzenia, a nie końcowy efekt pracy.

OOP nie działa dobrze z programowaniem proceduralnym ponieważ koncentruje się na obiektach natomiast PP bazuje na procedurach (będziemy w tym artykule luźno definowali 'procedury' jako funkcje dostępne bez użycia technik obiektowych natomiast 'metody' jako funkcje, które można wywołać jedynie ze środka obiektu). Procedury, tak jak metody, są jedynie funkcjami, do któ?ych odwołuje się użytkownik, jednak są między nimi pewne różnice.

Procedury nie mają danych obiektowych, na których mogłyby pracować. Dane muszą być im dostarczane w postaci listy argumentów, lub muszą one zawierać dane w obrębie siebie. Procedury mogą używać danych przekazanych jako argument lub ogólnych danych programu. Metody mogą używać jedynie danych w obrębie swojego obiektu. W efekcie, zasięg działania metody to zwykle obiekt w obrębie którego jest dana metoda.

Procedury często używają danych globalnych, mimo, że powinno się to dziać tylko w wypadku absolutnej konieczności. Metody używające danych globalnych powinny zostać napisane od nowa najszybciej, jak to tylko możliwe. Procedury często odwołują się do innych procedur z kilkoma parametrami, natomiast metody powinny mieć mało parametrów, za to częściej odwoływać się do innych metod.

Programowanie funkcjonalne (FP) trudno stosować razem z programowaniem obiektowym z kilku powodów. Najważniejszym jest to, że FP bazuje na konkretnych podejściach do rozwiązania problemu, natomiast OOP używa obiektów jedynie do wyrażenia koncepcji. Poza tym procedury FP mogą być używane wszędzie, w przeciwieństwie do metod, których można używać tylko w obrębie obiektu, do którego należą.

Wiedząc to wszystko możemy wyjaśnić, dlaczego Perl jest jednym z najlepszych języków do łączenia metod OOP, PP i FP.

Jak Perl łączy OOP, FP i PP?

Perl jest luźnym językiem. Robi bardzo wiele, aby programista mógł robić co tylko chce, niezależnie od tego, czy mu to wyjdzie na zdrowie. Kontrastuje to z jezykami takim jak C++ czy Java. Na przykład - Perl pozwala programistom tworzyć i używać zmiennych bez wcześniejszej deklaracji (chociaż nie jest to zalecane, a często jest wręcz niedozwolone na skutek stosowania zalecenia 'use strict'). Pozwala też na dostęp do wewnętrznych danych obiektu, zmianę klas i redefiniowanie metod 'w locie'. Perlowe podejście do programowania to pozwalanie człowiekowi na wszystko w imie lepszego kodowania i debugowania. Pomaga tym samym wykonać zadanie, jest więc najlepszym przyjacielem lub najzagorzalszym wrogiem programisty.

Dlaczego ktoś chciałby mieszać OOP, PP i FP, skoro jest to łamanie reguł? Posuńmy się krok w tył i przemyślmy pytanie. Czym są OOP, PP i FP? Są tylko metodami, zbiorami koncepcji i reguł stworzonych po to, aby służyły programistom. OOP, PP i FP są narzędziami, a pierwszym zadaniem każdego programisty jest poznanie swoich narzędzi. Programista, który tworzy narzędzia na skutek niewiedzy o ich istnieniu marnuje czas, pieniądze i wysiłek.

Programista może zadowolić się narzędziami, które zna najlepiej i to jest chyba najgorsze, co może mu się przydarzyć. Używanie jedynie podzbioru narzędzi dostępnych w przedsiębiorstwie to gwarancja, że programista stanie się za kilka lat bezużyteczny. Programista powinien móc mieszać metody o ile pozwala mu to na bycie bardziej efektywnym, ulepsza jego kod a jego grupe czyni bardziej innowacyjną. Perl rozumie i wspiera takie podejście.

Korzyści z OOP

Jest ich za dużo, aby wymienić wszystkie w tym artykule a mały zbiór najważniejszych to: łatwość powtórnego użycia kodu, ulepszania jakości kodu, spójny interfejs i adaptowalność.

Jako że OOP bazuje na zbiorze klas i obiektów, powtórne użycie kodu to po prostu importowanie tych klas w razie potrzeby. Jest to jak dotąd najważniejsza pojedyncza przyczyna używania OOP oraz powód jego popularności.
Są w tym jednak pewne wady - na przykład rozwiązania dawnych problemów mogą nie być idealne do problemów obecnych a zrozumienie biblioteki ze złą dokumentacją może zająć tyle czasu co napisanie jej od nowa. Unikanie takich wpadek jest zadaniem inżyniera oprogramowania.

Jakość kodu polepsza się ponieważ 'stopień obudowania' (encapsulation) ogranicza psucie danych, a dziedziczenie i polimorfizm zmniejszają ilość i złożoność nowego kodu, który trzeba napisać. Należy delikatnie balansować pomiędzy jakośćią kodu a innowacyjnością programowania - najlepiej zostawić to do odkrycia grupie, ponieważ w całości zależy to od jej przygotowania i celu.

Adaptowalność jewt nieco mglistą koncepcją w programowaniu. Zdefiniowałbym ją jako przewidywanie i akceptacjia zmian w użytkowaniu i środowisku działania programu. Adaptowalność jest ważna dla dobrze napisanego software'u ponieważ każde oprogramowanie musi współdziałać z otaczającym światem, a dobrze napisane oprogramowanie musi robić to z gracją. OOP ułatwia to za pomocą modułowej budowy, dzięki której zmiany fragmentów kodu spowodowane zmianami środowiska nie muszą wpływać na zmiane rdzenia architektury programu.

Jak używać OOP w Perlu

Czy w to wierzysz czy nie, OOP w Perlu nie jest trudne na poziomie podstawowym czy średnio zaawansowanym, a nawet użycie zaawansowane nie jest bardzo skomplikowane.

Perl stara się nakładać na programiste najmniej ograniczeń jak to możliwe. OOP w Perlu jest (wybaczcie porównanie) jak grillowanie. Każdy przynosi własne jedzenie i przyrządza je jak chce. Jest tu nawet zbiorowa atmosfera grillowania - niepowiązane obiekty mogą dzielić się danymi.

Pierwszym krokiem jest zrozumienie pakietów (packages) Perla. Pakiety są jak przestrzenie nazw w C++ i biblioteki w Javie: są ogrodzeniami dzielącymi dane na poszczególne obszary. Jednakże pakiety w Perlu są jedynie 'doradcami' dla programisty. Domyślnie Perl nie zabrania wymiany danych pomiędzy pakietami (chociaż programista może to zrobić poprzez zmienne leksykalne.

Listing 1. Nazwy pakietów, zmienianie pakietów, dzielenie danych pomiędzy pakietami, zmienne pakietowe.

#!/usr/bin/perl
# uwaga: poniższy kod wygeneruje ostrzeżenia podczas użycia z przełącznikiem -w,
# a nawet nie skompiluje się przy "use strict". Ma on na celu zademonstrowanie
# zmiennych pakietowych i leksykalnych. Powinieneś zawsze używać "use strict".
# Zwróć uwagę na każdą linijke!
# To jest globalna zmienna pakietowa; Nie powinieneś ich używać z "use strict"
# Wszystko mieści się w pakiecie "main"
$global_sound = "
";
package Krowa; # Tu zaczyna się pakiet krowa
# To jest zmienna pakietowa, dostępna ze wszystkich innych pakietów jako $Krowa::sound
$sound = "moo";
# To jest zmienna leksykalna, dostępna wszędzie w tym pliku
my $extra_sound = "stampede";
package Swinia; # Tu zaczyna się pakiet Swinia, a pakiet Krowa się kończy
# To jest zmienna pakietowa dostępna ze wszystkich innych pakietów jako $Pig::sound
$Pig::sound = "oink";
$::global_sound = "pigs do it better"; # inna zmienna pakietu main
# Wracamy do pakietu main
package main;
print "Krowy robia: ", $Krowa::sound; # wyswietla "moo"
print "\nSwinie robia: ", $Swinia::sound; # wyswietla "oink"
print "\nExtra sound: ", $extra_sound; # wyswietla "stampede"
print "\nWhat's this I hear: ", $sound; # $main::sound jest niezdefiniowane!
print "\nEveryone says: ", $global_sound; # wyswietla "pigs do it better"

#koniec Listingu 1.

Zauważ, że ogólno-plikowa zmienna leksykalna $extra_sound jest dostępna we wszystkich trzech pakietach, bo są one zdefiniowane w obrębie tego samego pliku (w tym przykładzie). Normalnie każdy pakiet zdefiniowany jest we własnym pliku co daje pewność, że zmienne leksykalne są prywatne dla każdego pakietu. Tak więc da się osiągnąć 'obudowanie' (encapsulation) pakietów w Perlu (po więcej informacji sprawdź 'perldoc perlmod').

Następnie musimy znaleść jakąś zależność między pakietami a klasami. W wypadku Perla klasy to po prostu rodzaj pakietów (w przeciwieństwie do obiektów, które są swoiśćie tworzone funkcją 'bless()'). Tu po raz kolejny Perl rozluźnia zasady OOP, tak aby nie krępowały programisty.

Metoda 'new()' jest zwyczajowym konstruktorem klasy (chociaż, jak to zwykle w Perlu, możesz nazwać ją jak chcesz). Jest ona wywoływana zawsze wtedy, kiedy klasa staje się obiektem.

Listing 2. - Klasa Barebones

#!/usr/bin/perl -w
package Barebones;
use strict;
# konstruktor tej klasy nie przyjmuje żadnych parametrów
sub new
{ my $classname = shift; # znamy nazwę naszej klasy
bless {}, $classname; # i bless'ujemy niezdefiniowany hash
}
1;

Możesz sprawdzić ten kod samodzielnie umieszczając powyższy kod w oddzielnym pliku o nazwie Barebones.pm w jakimkolwiek katalogu i uruchamiając w tym katalogu następującą komendę (tworzącą obiekt Barebones):

perl -I. -MBarebones -e 'my $b = Barebones->new()'

Możesz też na przykłąd umieścić w new() rozkaz print żeby zobaczyć co przechowyuje zmienna $classname
Jeśli zamiast Barebones->new() użyjesz Barebones::new(), nazwa klasy nie zostanie przekazana do new(). Inaczej mówiąc new() nie zadziała jak konstruktor, ale jak prosta funkcja.

Pewnie zastanawiasz się, do czego potrzebne jest przekazanie $classname i dlaczego zamiast tego nie napisać po prostu:
Bless {}, "Barebones";

Odpowiedzią jest dziedziczenie - konstruktor ten może być wywołany przez klasę, która dziedziczy coś od Barebones, ale sama się tak nie nazywa. Blessowałbyś złą funkcję ze złą nazwą, a to zły pomysł w OOP.

Każda funkcja potrzebuje danych i metod innych niż new(). Uzyskanie tego to nic innego jak napisanie kilku prostych procedur.

Listing 3. Klasa z własnymi danymi i metodami.

#!/usr/bin/perl -w
package Barebones;
use strict;
my $count = 0;
# ta klasa nie przyjmuje żadnych parametrów konstruktora
sub new
{ my $classname = shift; # znamy nazwe klasy
$count++; # zapamiętaj ilość obiektów
bless {}, $classname; # I bless'uj anonimowy hash
}sub count
{ my $self = shift; # A to sam obiekt
return $count;
}1;
#koniec listingu 3.

Możesz sprawdzić ten obiekt pisząc:

perl -I. -MBarebones -e 'my $b = Barebones->new(); Barebones->new(); print $b->count'

W rezultacie powineneś otrzymać 2. Konstruktor jest wywoływany dwa razy modyfikując zmienną leksykalną $count , która jest zamknięta w _pakiecie_ Barebones a nie w każdym obiekcie tego pakietu. Lokalne dane obiektu powinny być przechowywane w samym obiekcie. W wypadku Barebones jest to anonimowy hash bless'owany jako obiekt. Zauważ, że możemy uzyskać dostęp do obiektu zawsze kiedy jego metody są przywoływane, poniewać odwołanie do obiektu jest pierwszym parametrem przekazywanym do tych metod.

Istnieje kilka specjalnych metod takich jak DESTROY() i AUTOLOAD(), do których odwołuje się automatycznie sam Perl w pewnych warunkach. AUTOLOAD() jest przechwytującą wszystko metodą pozwalającą na używanie dynamicznych nazw metod. DESTRUCT() jest destruktorem obiektu, ale nie powinno się go używać jeśli nie jest to naprawde konieczne. Używanie go dowodzi, że ciągle myślisz jak programista C/C++.

Teraz zajmijmy się dziedziczeniem. W Perlu odbywa się ono poprzez modyfikacje zmiennej @ISA. Po prostu przypisujesz do zmiennej liste nazw klas i tyle. Do @ISA możesz wstawić cokolwiek. Jako rodzica swojej klasy możesz wpisać nawet Szatana i Perl się nie obrazi (chociaż Twój ksiądz, pastor, rabin czy imam może).

Listing 4. - Dziedziczenie

#!/usr/bin/perl -w
package Barebones;
# Dodaj ten kod na początku swojego modułu, przed całą resztą i deklaracjami zmiennych
require Animal; # klasa - rodzic
@ISA = qw(Animal); # ogłasza, że klasa jest dzieckiem 'zwierzęcia'
# zauważ, że @ISA z założenia jest zmienną globalną, i "use
# strict" znajduje się za jej deklaracją.
use strict;
use Carp;
# zrób swoją metodę new() wyglądającą mniej więcej tak:
sub new
{ my $proto = shift;
my $class = ref($proto) || $proto;
my $self = $class->SUPER::new(); # używaj metody new() należącej do rodzica
bless ($self, $class); # ale bless'uj $self (Animal) jako Barebones
}1;

#koniec listingu 4.

To najbardziej Podstawowa wiedza o OOP w Perlu. Jest w nim jeszcze wiele rzeczy, które powinieneś zgłębić. Napisano w tym temacie wiele dobrych książek - zajrzyj do bibliografii.

h2xs: Twój nowy najlepszy przyjaciel

Chciałbyś, aby ktos napisał za Ciebie klasy w Perlu, spisał szkielet dokumentacji (POD) i ogólnie uczynił Twoje życieprostszym? Perl daje Ci takie narzędzie: h2xs

Jego najważniejsze opcje to "-A -n Module". Kiedy ich użyjesz, h2xs stworzy szkielet katalogu nazwanego Module pełnego przydatnych plików:

  • Module.pm - moduł jako taki, z wpisanym szkieletem dokumentacji
  • Module.xs - dla łączenia z kodem C (odpal 'perldoc perlxs' po więcej informacji)
  • MANIFEST - lista plików do tworzenia pakietu (do dystrybucji)
  • test.pl - szkielet skryptu testującego
  • Changes - dziennik zmian w module
  • Makefile.PL - generator makefile'i (do użycia z 'perl Makefile.PL')

Nie musisz ich wszystkich używać, ale miło jest wiedzieć, że są, jeślibyś ich potrzebował.

Ćwiczenia:

  1. Jakie są różnice między OOP, PP i FP?
  2. Jakie są najważniejsze cechy języka zorientowanego obiektowo?
  3. Kiedy unikałbyś OOP?
  4. Napisz klase z metodą new() przechowującą aktualną ilość obiektów w samym tworzonym obiekcie, jako rodzaj jego identyfikatora. Dlaczego to jest/nie jest dobry pomysł?
  5. Jeśli wszystkie obiekty dziedziczyłyby z jednego źródła, bazowego obiektu wszystkich innych obiektów, to jakie cechy i metody umieściłbyś w tym obiekcie? Dlaczego jest to niekoniecznie dobry pomysł?
  6. przypatrz się dokłądnie plikom generowanym przez h2xs i Makefile'owi tworzonemu przez Perl z Makefile.PL

Bibliografia:
-4 poprzedzające rozdziały serii Teodora Zlatanova, dostępne pod adresami:
Rozdział 1 i wprowadzenie - http://www-106.ibm.com/developerworks/library/l-road1.html
Rozdział 2 (ogónie o kodzie) - http://www-106.ibm.com/developerworks/library/l-road2.html
Rozdział 3 (o pętlach) - http://www-106.ibm.com/developerworks/library/l-road3.html
Rozdział 4 (o programowaniu funkcjonalnym) - http://www-106.ibm.com/developerworks/library/l-road4.html

-Perl zorientowany obiektowo Damiana Conwaya - http://www.manning.com/Conway/index.html
-Dokumentacja Perla (perldoc) - rozdziały: 'perltool', 'perlmod', 'perlref', 'perlobj', 'perlbot', 'perltie'
-FAQ grupy comp.object - http://www.cyberdyne-object-sys.com/oofaq2/index.htm
-Perl Programowanie - Larry'ego Walla i Toma Christiansena (wydawnictwa O'Reilly - http://www.oreilly.com)

-Inne Publikacje Teodora Zlatanova na DeveloperWorks (seria Cultured Perl)


copyrights PRO - Magazyn Prawdziwych Internautów 2002 [ aktualności | komputery | internet | kultura ] 20
<--poprzednia strona | do góry | spis treści | następna strona-->