Spis treści:

Podstawy Pythona: darmowy kurs dla wszystkich poziomów umiejętności Cztery imponujące projekty do Twojego portfolio Bezpośrednia interakcja z nauczycielem: wyjątkowa okazja do nauki
Dowiedz się więcej
Rhea Mutafis
O autorce
Przedsiębiorca z doktoratem z filozofii na Sorbonie i tytułem MBA z CDI. Pisał dla takich publikacji jak TheNextWeb, HP Enterprise i Built In.

Na początku ery programowania, w latach 60. XX wieku, maszyny liczące miały ograniczone możliwości. Wymagało to efektywnego podziału zasobów między zadania i dane.
Problem polegał na tym, że w tamtym czasie nie było możliwe wydajne przetwarzanie dużych ilości informacji bez przeciążania komputera do granic możliwości. Jeśli zachodziła potrzeba rozwiązania wielu różnych problemów, każdy z nich mógł działać tylko z ograniczoną ilością danych, w przeciwnym razie obliczenia ciągnęłyby się w nieskończoność.
W połowie lat sześćdziesiątych Alan Kay przedstawił koncepcję niezależnych minikomputerów, które mogłyby wymieniać nie tylko dane, ale także wiadomości. Takie podejście znacznie poprawiłoby efektywność alokacji zasobów obliczeniowych.
Od tłumacza
Alan Kay, mający wykształcenie matematyczne i biologię molekularną, dostrzegł paralelę między komputerami a żywymi komórkami. Jego koncepcja zakładała, że niezależnie działające programy (jak komórki) mogłyby wymieniać między sobą komunikaty. Jednocześnie stan wewnętrzny programów pozostawałby zamknięty dla świata zewnętrznego, realizując zasadę enkapsulacji.
Kay dzieli się swoimi wspomnieniami w następujący sposób: „Wyobrażałem sobie obiekty jako coś w rodzaju komórek biologicznych lub pojedynczych komputerów połączonych w sieć, które mogłyby komunikować się wyłącznie za pomocą komunikatów”.
Chociaż sam pomysł był dość innowacyjny, wprowadzenie programowania obiektowego nie nastąpiło od razu i dopiero w 1981 roku zaczęło ono zyskiwać na popularności. Od tego czasu zarówno początkujący, jak i doświadczeni programiści coraz lepiej opanowują programowanie obiektowe. W rezultacie, dzisiejszy rynek jest przepełniony specjalistami w dziedzinie programowania obiektowego.
Programowanie obiektowe jest wiodącym paradygmatem w rozwoju oprogramowania od ponad czterdziestu lat. Jednak w ostatnich latach spotyka się z coraz większą krytyką. Czy nowe technologie po prostu wyprzedziły ten paradygmat?
Uzasadnienie integracji danych i metod: czy warto?
Istotą programowania obiektowego jest dość prosta zasada: dzielisz program na oddzielne, niezależne komponenty. Oznacza to, że wszystkie dane i funkcje potrzebne do pracy z tymi danymi grupujesz w jednym miejscu.
Należy pamiętać, że odnosi się to wyłącznie do koncepcji enkapsulacji, w której dane i funkcje są ukryte w obiekcie i niedostępne z zewnątrz, zapewniając w ten sposób ochronę. Interakcja z zawartością obiektu jest możliwa tylko za pomocą specjalnych komunikatów, znanych jako metody dostępowe, w tym gettery i settery.
Współczesne programowanie obiektowe ma również inne kluczowe koncepcje, które nie były częścią pierwotnego projektu. Należą do nich dziedziczenie i polimorfizm.
Dziedziczenie to możliwość tworzenia przez programistów podklas, które dziedziczą wszystkie cechy klasy nadrzędnej. Idea ta pojawiła się w 1976 roku, dziesięć lat po powstaniu programowania obiektowego.
Dekada później koncepcja polimorfizmu wkroczyła do programowania obiektowego. Ogólnie rzecz biorąc, oznacza to, że metoda lub obiekt może służyć jako podstawa do tworzenia innych metod i obiektów. Polimorfizm jest koncepcją bardziej elastyczną niż dziedziczenie, ponieważ pozwala nowemu bytowi nie tylko odziedziczyć wszystkie cechy oryginalnej metody lub obiektu, ale także zmodyfikować je zgodnie ze swoimi potrzebami.
Polimorfizm charakteryzuje się tym, że pomimo istnienia zależności między dwoma bytami w kodzie źródłowym, wywoływany byt pełni rolę swoistego szablonu dla rzeczywistych obiektów używanych w praktyce. To znacznie upraszcza zadanie programistów, ponieważ nie muszą się oni martwić o współzależności podczas wykonywania programu.
Ogólnie rzecz biorąc, koncepcje dziedziczenia i polimorfizmu występują nie tylko w kontekście programowania obiektowego. Jednak to właśnie hermetyzacja danych i powiązanych z nimi metod odróżnia programowanie obiektowe od innych podejść. W czasach, gdy moc obliczeniowa była znacznie ograniczona w porównaniu z dzisiejszą, ten pomysł był prawdziwie rewolucyjny.

Załóżmy, że zdecydujesz się na ponowne wykorzystanie klasy, której wcześniej używałeś w innym projekcie. Jakie będą konsekwencje zmiany klasy bazowej, z której dziedziczy?
Twój kod może być zagrożony, nawet jeśli zmiany wpłyną tylko na jego niewielką część. Na przykład, wczoraj wszystkie Twoje klasy pochodne działały bez zarzutu, ale dziś zaczynają się zawieszać. Było to spowodowane drobną poprawką w klasie bazowej, która jednak miała katastrofalny wpływ na cały projekt. Taka sytuacja nazywa się problemem kruchej lub „niezabezpieczonej” klasy bazowej.
Decyzja o ponownym wykorzystaniu klasy może wydawać się szybkim i skutecznym rozwiązaniem, ale później może wiązać się ze znacznymi kosztami. Im częściej korzystasz z dziedziczenia, tym trudniej jest utrzymać kod.
Dziedziczenie można opisać jako delikatny i płynny proces, w którym cechy jednej klasy są przekazywane innej. Ale co, jeśli trzeba połączyć cechy dwóch różnych klas?
Niestety, nie da się tego zrobić prosto i płynnie.
Rozważmy na przykład klasę Copier, która reprezentuje urządzenie kopiujące. Urządzenie to jest w stanie skanować dokumenty, a następnie reprodukować je na nowej, czystej kartce papieru.
Kwestia, do której podklasy powinna należeć kopiarka — skanera czy drukarki — jest istotna dla zrozumienia jej funkcjonalności. Kopiarka, jako urządzenie przeznaczone do kopiowania dokumentów, może mieć cechy zarówno skanera, jak i drukarki. Jednak biorąc pod uwagę jej główny cel, bardziej logiczne jest zaklasyfikowanie jej jako podklasy drukarki, ponieważ urządzenie kopiujące zazwyczaj obejmuje funkcje drukowania, co jest kluczowym aspektem jej działania. Z drugiej strony, obecność skanera w funkcji kopiarki jest również istotna, ponieważ bez możliwości skanowania oryginałów urządzenie nie może wykonywać swoich podstawowych funkcji. Ostatecznie decyzja o tym, do której podklasy przypisać klasę Copier, zależy od tego, które aspekty jej działania są uważane za podstawowe — drukowanie czy skanowanie.
Nie ma jednego poprawnego rozwiązania. Ta sytuacja jest znana jako problem diamentu. Chociaż kod pozostaje nienaruszony, często wywołuje frustrację wśród programistów.
Poprzednie pytanie dotyczyło przodka klasy Copier. Wprowadziłem Cię w błąd odpowiedzią: istnieje eleganckie rozwiązanie tej sytuacji. Uczyńmy klasę Copier klasą nadrzędną, a Scanner i Printer jej klasami potomnymi, które odziedziczą tylko niektóre z jej właściwości. To wszystko!
To naprawdę interesujące pytanie. Wyobraźmy sobie jednak, że mamy urządzenie Copier, które drukuje tylko w czerni i bieli, oraz drukarkę, która może wytwarzać wydruki kolorowe. Czy w tym przypadku nie uważasz, że „Drukarka” jest bardziej uniwersalnym terminem niż „Kopiarka”? Pojawia się kolejne pytanie: co jeśli drukarka wymaga połączenia Wi-Fi, a kopiarka może działać bez niego?
Wraz ze wzrostem liczby właściwości w klasie, coraz trudniej jest zdefiniować prawidłową hierarchię. W tym przypadku mówimy o właściwościach klas takich jak kopiarka i drukarka, które zawierają zarówno elementy wspólne, jak i specyficzne. Próba uporządkowania tych klas w liniową hierarchię dziedziczenia, szczególnie w dużym i złożonym projekcie, może prowadzić do różnych trudności. Problem polega na tym, że nie można jednoznacznie wybrać klasy nadrzędnej spośród klas Kopiarka, Drukarka i Skaner.

Kiedy masz problemy z hierarchią, możesz rozważyć alternatywę: skupmy się na programowaniu obiektowym bez struktur hierarchicznych. Moglibyśmy po prostu użyć kolekcji właściwości, dziedzicząc po nich i wprowadzając modyfikacje lub uzupełnienia w razie potrzeby. Choć to podejście może wydawać się nieco mylące, wciąż jest całkiem wykonalne, prawda?
Niestety, tak nie jest. To wprowadza kolejny problem. Główną ideą enkapsulacji jest efektywniejsze zarządzanie danymi przy jednoczesnej ochronie ich przed sobą nawzajem. Bez jasnej hierarchii jest to niemożliwe do osiągnięcia.
Załóżmy, że obiekt A musi oddziaływać z obiektem B. Nie ma znaczenia, jaka jest dokładna relacja, o ile A nie jest bezpośrednim potomkiem B.
Obiekt A zawiera odwołanie do obiektu B, co pozwala im na interakcję ze sobą. Jeśli jednak A posiada dane, do których dostęp mają również dzieci B, stwarza to sytuację, w której dane mogą być modyfikowane z wielu źródeł. W rezultacie informacje w obiekcie B tracą ochronę, a zasada enkapsulacji zostaje naruszona.
Pomimo faktu, że znaczna liczba programistów pracujących z podejściem obiektowym tworzy swoje produkty z wykorzystaniem takiej architektury, nie można już tego nazwać prawdziwym programowaniem obiektowym. Staje się to raczej bałaganem.
Ryzyko związane z jednym paradygmatem
Wszystkie wymienione trudności związane z programowaniem obiektowym mają jedną wspólną cechę: dziedziczenie występuje nawet w przypadkach, gdy nie jest to optymalne rozwiązanie. Warto zauważyć, że trudności te nie są unikalne dla podejścia obiektowego, ponieważ początkowo nie implikowało ono dziedziczenia. Znacznie istotniejszy jest tu strach przed odejściem od ustalonego paradygmatu i ślepym trzymaniem się go. Jednak nadmierne trzymanie się jednego paradygmatu nie ogranicza się do programowania obiektowego. W kontekście programowania funkcyjnego, na przykład, pojawiają się znaczne trudności podczas pracy z wprowadzaniem i wyświetlaniem danych. W przypadku takich problemów bardziej odpowiednie są metody obiektowe lub proceduralne. Niektórzy programiści wolą jednak implementować wejście i wyjście jako czyste funkcje, co powoduje, że ich kod staje się nadmiernie uciążliwy i trudny do zrozumienia. Jednocześnie alternatywne podejścia mogą rozwiązać te problemy za pomocą zaledwie kilku przejrzystych linijek kodu. W różnych paradygmatach, podobnie jak w naukach religijnych, ważna jest równowaga. Z pewnością postacie takie jak Jezus, Mahomet i Budda przyniosły światu wybitne idee. Jednak ścisłe przestrzeganie ich instrukcji w najdrobniejszych szczegółach może mieć znaczący negatywny wpływ zarówno na życie osobiste, jak i życie innych. Ważne jest znalezienie równowagi.
Z pewnością programowanie funkcyjne zyskuje na popularności, podczas gdy programowanie obiektowe traci na znaczeniu w ostatnich latach i spotyka się z ostrą krytyką.
Niewątpliwie opanowanie nowych paradygmatów jest ważne, ale ich zastosowanie powinno opierać się na rzeczywistych potrzebach. W końcu, nawet jeśli programowanie obiektowe jest postrzegane jako uniwersalne narzędzie, z którego programiści korzystają do wszystkich zadań, czy to powód, by z niego rezygnować? Rozsądniejszym rozwiązaniem może być uzupełnienie zestawu narzędzi o śrubokręt, nóż i nożyczki, co pozwoli Ci wybrać narzędzie najbardziej odpowiednie do rozwiązania konkretnego problemu.

