Myślenie zorientowane obiektowo

Zorientowany obiektowo jest chwytliwym zwrotem. Nazywanie czegokolwiek obiektowo zorientowanym może sprawić, że brzmi to całkiem inteligentnie. Ruby twierdzi, że jest językiem skryptowym zorientowanym obiektowo; ale co dokładnie oznacza "zorientowany obiektowo"?

Tu będą różne odpowiedzi na to pytanie, które można by prawdopodobnie sprowadzić do tego samego. Zamiast podsumować to zbyt szybko, pomyślmy przez chwilę o tradycyjnym paradygmacie programowania.

Tradycyjnie, problem programowania jest atakowany przez wymyślanie niektórych rodzajów reprezentacji danych oraz procedur, które działają na tych danych. W tym modelu, dane są obojętne, bierne i bezradne; siedzi to w całkowitej łasce z dużym proceduralnym ciałem, które jest aktywne, logiczne i wszechmocne.

Problemem z tego podejścia jest to, że programy są pisane przez programistów, którzy są tylko ludźmi i mogą przechować tylko pewną ilość detali w myślach w dowolnym momencie. Jak projekt się rozrasta, jego rdzeń proceduralne rośnie do punktu, w którym trudno jest pamiętać, jak działa całość. Drobne pomyłki myślenia i błędy typograficzne stają się bardziej prawdopodobne i są w rezultacie dobrze ukrytymi błędami. Złożone i niezamierzone interakcje zaczynają pojawiać się wewnątrz rdzenia procesowego i utrzymanie staje się próbą która jak zła kałamarnica chce Ci na okrągło ze złości przyczepić macki do twarzy. Istnieją wytyczne dla programów, które mogą pomóc zminimalizować i zlokalizować błędy w tym tradycyjnym paradygmacie, ale jest lepsze rozwiązanie, które polega na fundamentalnej zmianie sposobu w jaki pracujemy.

Co zorientowane-obiektowo programowanie robi to pozwala nam przekazywać większość przyziemnych i monotonnych czynności logicznych do samych danych; to zmienia naszą koncepcję danych z pasywnych do aktywnych. Innymi słowy,

  1. Przestajemy traktować każdy kawałek danych jak pudełko z otwartą pokrywą, które pozwala nam do niego dotrzeć i rzucać czymś wokół.
  2. Zaczynamy traktować każdy kawałek danych jak pracującą maszynę, z zamkniętą pokrywą i kilkoma dobrze oznakowanymi przełącznikami i pokrętłami.

To co zostało opisane powyżej jako "maszyna" może być bardzo prosta lub złożona wewnątrz; nie możemy powiedzieć z zewnątrz, a my nie pozwolimy sobie otworzyć maszyny (z wyjątkiem, gdy jesteśmy absolutnie pewni, że coś jest nie tak z jego konstrukcją), więc wymagamy robienia takich rzeczy jak ciśnięcia przełączników i czytania pokręteł do współdziałania z danymi. Gdy urządzenie jest zbudowane, nie chcemy myśleć o tym, jak to działa.

Można by pomyśleć, że sprawiamy sobie więcej roboty, ale takie podejście stwarza dobrą tendencję przy zapobieganiu wszelkiego rodzaju błędom.

Zacznijmy od przykładu, który jest zbyt prosty aby mieć znaczenie praktyczne, ale powinien zilustrować przynajmniej część tej koncepcji. Twój samochód ma miernik długości trasy. Jego zadaniem jest śledzenie odległości, którą przebył pojazd od czasu ostatniego jego naciśnięcia przycisku. Jak moglibyśmy przedstawić ten model w języku programowania? W C, miernik byłby po prostu zmienną numeryczną, ewentualnie typu float. Program mógłby manipulować tą zmienną zwiększając swoją wartość w małych krokach, ze sporadycznymi przypadkami, kiedy zostaje wyzerowany. Co w tym złego? Błąd w programie mógłby przypisać błędną wartość do zmiennej, z niezliczonej ilości powodów. Każdy, kto programował w C wie, jak to jest spędzić wiele godzin lub dni tropienia takich błędów, których przyczyna wydaje się absurdalnie prosta, gdy już została znaleziona. (Moment znajdowania błędu jest powszechnie rozpoznawalny przez odgłos głośnego klepnięcia w czoło.)

Ten sam problem można zaatakować z zupełnie innej strony, w kontekście obiektowym. Pierwszą rzeczą robioną przez programistę przy projektowaniu miernika nie jest zadawanie pytania "który ze znanych typów danych najbardziej przypomina tę rzecz?" ale "jak właściwie ta rzecz powinna działać?" Różnica stawianych pytań jest znacząca. Konieczne jest poświęcenie chwili decydując dokładnie czym jest drogomierz i w jaki sposób wchodzi w interakcje ze światem zewnętrznym. Decydujemy się zbudować małą maszynę z kontrolą, która pozwala nam inkrementować licznik, resetować go, odczytywać jego wartość i nic więcej.

Nie zapewniamy dla miernika sposobu do przypisywania dowolnych wartości; czemu? ponieważ wszyscy wiemy, że drogomierze nie działają w ten sposób. Istnieje tylko kilka rzeczy, które drogomierz powinien być zdolny wykonywać i do których damy mu wszelkie pozwolenia. Tak więc, jeśli coś innego w programie błędnie próbuje umieścić jakąś inną wartość (powiedzmy, obiekt temperatury z kontroli klimatu pojazdu) do miernika, to jest bezpośrednim wskazaniem tego, co poszło źle. Mówi się nam, kiedy uruchomimy program (lub ewentualnie podczas kompilacji, w zależności od charakteru danego języka), że nie pozwalamy na przypisywanie dowolnych wartości do obiektów miernika. Ten komunikat może być niejasny, ale będzie rozsądnie zamknąć to tutaj. To nie wyklucza błędu, prawda? Ale szybko kieruje nas w stronę przyczyny. Jest to tylko jeden z wielu sposobów w jaki OOP może zaoszczędzić dużo zmarnowanego czasu.