Ruby/Myślenie zorientowane obiektowo
Myślenie zorientowane obiektowo
edytujZorientowany obiektowo to chwytliwe określenie. Powiedzenie o czymkolwiek, że jest "zorientowane obiektowo" brzmi naprawdę mądrze. Ruby określa się jako język skryptowy zorientowany obiektowo, ale co to naprawdę znaczy "zorientowany obiektowo"?
Istnieje wiele różnorodnych odpowiedzi na to pytanie, które można by prawdopodobnie sprowadzić do tego samego. Zamiast jednak zbyt szybko podsumowywać to zagadnienie, spróbujmy pomyśleć przez chwilę o tradycyjnym paradygmacie programowania.
Tradycyjnie, problem programowania rozwiązywany jest przez podejście, w którym obecne są różne typy reprezentacji danych oraz procedury które na tych danych operują. W modelu tym dane są obojętne, pasywne i bezradne; oczekują na łaskę dużej proceduralnej bryły, która jest aktywna, logiczna i wszechmocna.
Problem w tym podejściu jest taki, że programy są pisane przez programistów, którzy są tylko ludźmi i potrafią pamiętać i kontrolować w danym czasie tylko pewną skończoną ilość detali. Jak projekt staje się większy jego proceduralny rdzeń rośnie do punktu, w którym trudno jest już pamiętać jak cała rzecz działa. Drobne pomyłki w sposobie myślenia i usterki typograficzne stają się przyczyną dobrze zamaskowanych błędów. Złożone i niezamierzone interakcje zaczynają wynurzać się z proceduralnego rdzenia i zarządzanie tym zaczyna przypominać noszenie w kółko wściekłej kałamarnicy i walkę z jej mackami. Są pewne wytyczne dotyczące programowania, które mogą pomóc zminimalizować i zlokalizować błędy w tym tradycyjnym paradygmacie, ale jest lepsze rozwiązanie które pociąga za sobą fundamentalną zmianę sposobu pracy.
Programowanie zorientowane obiektowo pozwala nam przekazywać większość przyziemnych i monotonnych czynności logicznych do samych danych; zmienia to nasze pojmowanie danych z pasywnych na aktywne. Innymi słowy:
- Przestajemy traktować każdy kawałek danych jak skrzynkę z otwartym wiekiem, do której możemy wkładać lub wyjmować przedmioty.
- Zaczynamy traktować każdy kawałek danych jak pracującą maszynę, z zamkniętą pokrywą i dobrze oznakowanymi przełącznikami oraz potencjometrami.
To co nazwaliśmy wyżej "maszyną" może być w środku bardzo proste lub bardzo złożone. Nie możemy tego określić patrząc z zewnątrz, jak i nie grzebiemy w jej wnętrzu (za wyjątkiem sytuacji, gdy jest absolutnie pewnie, że coś jest nie tak z jej projektem), więc jedyne czego się od nas wymaga by wpływać na dane to przekręcanie przełączników i odczytywanie potencjometrów. Jak już maszyna jest zbudowana nie chcemy sobie dalej zaprzątać głowy tym, jak ona działa.
Możesz sądzić, że po prostu robimy sobie więcej roboty, ale tak naprawdę to dobra robota w celu chronienia wielu rzeczy przed błędami.
Zacznijmy od przykładu, który jest zbyt prosty by mieć wartość praktyczną, ale powinien zilustrować przynajmniej jedną cześć tej koncepcji. Twój samochód ma tripmeter[1]. Jego celem jest rejestrowanie odległości którą przebył pojazd od momentu naciśnięcia przycisku. Jak moglibyśmy wymodelować to w języku programowania? W C, tripmeter byłby po prostu zmienną numeryczną, możliwe że typu float. Program mógłby manipulować tą zmienną zwiększając jej wartość przyrostowo małymi krokami, z okazjonalnym resetowaniem jej wartości do zera, jeśli zaszłaby taka potrzeba. A co w tym złego? Z nieokreślonej liczby niespodziewanych powodów błąd w programie mógłby przypisać błędną wartość do zmiennej. Każdy, kto programował w C wie, co to znaczy spędzać godziny lub dni próbując ustalić gdzie tkwi taki błąd. Jego przyczyna, jak się już ją odkryje, wydaje się absurdalnie głupia. (Moment znajdowania błędu jest przeważnie rozpoznawalny przez odgłos głośnego klepnięcia w czoło.)
Ten sam problem można zaatakować z zupełnie innej strony, w podejściu zorientowanym obiektowo. Pierwszym pytaniem, które zadaje programista, gdy projektuje tripmeter nie jest "jaki znany mi typ danych odpowiada najbliżej tej rzeczy?" ale "jak właściwie ta rzecz ma działać?" Różnica jest zasadnicza. Potrzeba poświęcić odrobinę czasu ustalając po co dokładnie jest drogomierz i w jaki sposób zewnętrzny świat zamierza się z nim kontaktować. Decydujemy się zbudować małą maszynkę z metodami regulacji które pozwolą nam zwiększać wartość, czytać ją, kasować, i nic poza tym.
Nie dostarczamy żadnego sposobu na przypisanie do tripmetera arbitralnych wartości. Dlaczego? Ponieważ wszyscy wiemy, że drogomierze nie działają w ten sposób. Jest tylko kilka rzeczy, które powinieneś robić z tripmeterem, i to wszystko na co pozwalamy. Zatem, jeśli coś innego w programie błędnie spróbuje umieścić jakąś inną wartość (powiedzmy, docelową temperaturę ze systemu sterowania klimatyzacją w samochodzie) w tripmeterze, dostaniemy natychmiastową wskazówkę co poszło nie tak. Będziemy poinformowani, gdy program zostanie uruchomiony (lub podczas kompilacji, zależnie od natury języka programowania), że nie mamy prawa przypisywać arbitralnych wartości do obiektów Tripmeter. Wiadomość może nie będzie dokładnie tak jasna, ale będzie ona sensownie zbliżona. Nie uchroni nas to przed błędem, prawda? Ale za to szybko wskaże nam, gdzie mniej więcej leży przyczyna błędu. To tylko jeden z kilkunastu sposobów, w jaki OOP może nam zaoszczędzić dużo zmarnowanego czasu.
Zazwyczaj robimy jeszcze jeden krok w abstrakcji, ponieważ okazuje się, że równie łatwo jak naszą maszynę można zbudować całą fabrykę która tworzy takie maszyny. Prawdopodobnie nie budowalibyśmy bezpośrednio pojedynczego tripmetera, raczej zbudowalibyśmy dowolną ilość tripmeterów z pojedynczego wzorca. Ten wzorzec (lub jak wolisz, fabryka tripmeterów) odpowiada temu co nazywamy klasą. Indywidualny tripmeter wygenerowany z tego wzorca (lub zbudowany przez fabrykę) odpowiada obiektowi. Większość języków obiektowych, wymaga by klasa była zdefiniowana nim będziemy mogli uzyskać nowy rodzaj obiektu, ale nie Ruby.
Warto tu zanotować, że użycie języka zorientowanego obiektowo nie wymusza odpowiedniego zorientowanego obiektowo projektu. W rzeczy samej, pisanie kodu, który jest niejasny, niechlujny, źle zaczęty, pełny błędów i chwiejący się na wszystkie strony, możliwe jest w każdym języku. To co Ruby robi dla ciebie (szczególnie w przeciwieństwie do C++) to to, że praktyka programowania obiektowego jest na tyle naturalna, że nawet gdy pracujesz w małej skali nie czujesz potrzeby by uciec się do brzydkiego kodu by zaoszczędzić sobie wysiłku. Będziemy omawiać sposoby, w których Ruby osiąga ten wspaniały cel, w miarę postępu tego podręcznika. Następnym tematem będą "przełączniki i potencjometry" (metody obiektów) a stamtąd przeniesiemy się do "fabryk" (klas). Jesteś wciąż z nami?
Przypisy
- ↑ Urządzenie elektroniczne, które rejestruje czas pracy pojazdu, liczbę przejechanych kilometrów, zużycie paliwa oraz inne parametry - przyp. tłum.