CodeGym /Blog Java /Poland /Paradygmaty programowania obiektowego
Autor
Jesse Haniel
Lead Software Architect at Tribunal de Justiça da Paraíba

Paradygmaty programowania obiektowego

Opublikowano w grupie Poland
Cześć! Czy zastanawiałeś/aś się kiedykolwiek, dlaczego Java jest skonstruowana tak, a nie inaczej? To znaczy, że deklarujesz klasy, tworzysz obiekty bazując na klasach, klasy mają metody... itp. Dlaczego ten język jest tak zorganizowany, że programy składają się z klas i obiektów, a nie z czegoś innego? Skąd w ogóle wzięła się koncepcja „obiektu” i dlaczego wyszła na prowadzenie? Czy wszystkie języki są skonstruowane w ten sam sposób? Jeśli nie, to jakie korzyści czerpie Java z tego paradygmatu? Jak widzisz, pytań jest wiele :) Spróbujmy na nie odpowiedzieć.

Jakie są zasady programowania obiektowego (OOP)?

Po pierwsze, Java nie jest stworzona z obiektów i klas dla kaprysu. To nie była ani zachcianka jej twórców, ani nawet ich wynalazek. Wiele innych języków również bazuje na obiektach. Pierwszym takim językiem była Simula, stworzona w latach 60. ubiegłego wieku w Norwegii. Koncepcje „klasy” i „metody” pojawiły się właśnie w Simuli.Metody statyczne w Javie - 1Kiedy spojrzymy na Simulę z perspektywy dzisiejszych standardów programowania, to zdaje nam się, że to jakiś starożytny język, nie można mu jednak odmówić „uderzającego podobieństwa” do Javy. Prawdopodobnie, jeśli przeczytasz kod napisany w Simuli, będziesz z grubsza potrafił/a powiedzieć, co on robi :)

Begin
	Class Rectangle (Width, Height); Real Width, Height;
			           
	 Begin
	    Real Area, Perimeter;  
	 
	    Procedure Update;      
	    Begin
	      Area := Width * Height;
              OutText("Rectangle is updating, Area = "); OutFix(Area,2,8); OutImage;
	      Perimeter := 2*(Width + Height);
              OutText("Rectangle is updating, Perimeter = "); OutFix(Perimeter,2,8); OutImage;
	    End of Update;
	 
	    Update;               
	    OutText("Rectangle created: "); OutFix(Width,2,6);
	    OutFix(Height,2,6); OutImage;
	 End of Rectangle;

       Rectangle Class ColouredRectangle (Color); Text Color;
			           
	Begin   	  
	    OutText("ColouredRectangle created, color = "); OutText(Color);
	    OutImage;
        End of ColouredRectangle;

 
      	 Ref(Rectangle) Cr;            
	 Cr :- New ColouredRectangle(10, 20, "Green"); 
End;
Ta próbka kodu pochodzi z Simula - 50 years of OOP (pol. Simula - 50 lat OOP). Jak widzisz, Java nie różni się wcale tak bardzo od swojej babci Simuli :) A oto, dlaczego tak jest: nowy paradygmat programowania obiektowego narodziły się wraz z Simulą. Wikipedia tak definiuje OOP: „Programowanie Obiektowe (OOP) to paradygmat w programowaniu, bazujący na koncepcji „obiektów”, które mogą zawierać dane w formie pól (często też nazywanych atrybutami) oraz kod w formie procedur (często też zwanych metodami).” Uważam, że to naprawdę dobra definicja. Uczysz się Javy dopiero od niedawna, ale w tej definicji nie znajdziesz żadnych słów, których nie znasz :) Dziś OOP to najpowszechniejsza metodologia w programowaniu. Poza Javą zasady programowania obiektowego używane są w wielu popularnych językach, o których być może słyszałeś/aś. Przykładowo, C++ (używany chętnie przy programowaniu gier), Objective-C i Swift (używany przy pisaniu programów na sprzęt Apple), Python (najpopularniejszy przy uczeniu maszynowym), PHP (jeden z najpopularniejszych języków programowania stron internetowych), JavaScript (prościej wymienić, do czego nie jest używany) i wiele innych. Ale wracając do tematu — czym właściwie są paradygmaty programowania obiektowego? Już mówię.

Paradygmaty OOP

To podstawa podstaw. Zasady programowania obiektowego składają się z 4 głównych filarów. Bez zrozumienia ich nie będziesz dobrym programistą.

Paradygmat 1. Dziedziczenie

Dobra wiadomość: znasz pierwszą zasadę OOP! :) Natknęliśmy się już na dziedziczenie parę razy w poprzednich lekcjach i nawet go używaliśmy. Dziedziczenie to mechanizm, który pozwala Ci opisać nową klasę, bazując na już istniejącej (macierzystej) klasie. Nowa klasa pożycza wtedy właściwości i funkcjonalność klasy macierzystej. A po co jest dziedziczenie i do czego może się przydać? Przede wszystkim, do ponownego użycia kodu. Pola i metody zadeklarowane w klasie macierzystej mogą zostać użyte w klasach potomnych. Jeśli wszystkie typy samochodów mają 10 pól wspólnych i 5 identycznych metod, po prostu przenosimy je do klasy macierzystej Car. Dzięki temu możemy bezproblemowo użyć ich w potomnych klasach. Ogromne zalety: zarówno ilościowe (mniej kodu), jak i jakościowe (klasy są prostsze). Co więcej, dziedziczenie jest bardzo elastyczne — możesz dopisać dodatkową funkcjonalność, której nie mają potomkowie (pewne pola lub zachowanie specyficzne dla danej klasy). W prawdziwym życiu wszyscy jesteśmy w pewnych względach podobni do naszych rodziców, ale także w wielu się różnimy :)

Paradygmat 2. Abstrakcja

To bardzo prosta zasada. Abstrakcja oznacza identyfikację podstawowej, najbardziej znaczącej cechy czegoś oraz odrzucenie wszystkiego, co drugorzędne i nieznaczące. Nie musisz wyważać otwartych drzwi. Przypomnij sobie przykład z tego starego wykładu o klasach. Załóżmy, że stworzyliśmy system katalogowania pracowników firmy. Aby utworzyć obiekty "employee", napisaliśmy klasę Employee. Jakie cechy powinny znaleźć się w tym katalogu? Imię i nazwisko, data urodzenia, nr ubezpieczenia zdrowotnego i ID pracownika. Mało prawdopodobne, że przyda nam się wzrost pracownika, jego kolor oczu czy włosów. Dla firmy jest to bez znaczenia. Zatem w klasie Employee zadeklarujemy następujące zmienne: String name, int age, int socialSecurityNumber i int employeeId. Odrzucamy wszelkie niepotrzebne informacje jak kolor oczu. Gdybyśmy jednak tworzyli system katalogowania dla agencji modelingu, sytuacja byłaby zupełnie inna. Ważne byłyby takie parametry modela, jak: height, eye color i hair color, ale zupełnie pominęlibyśmy nr ubezpieczenia zdrowotnego. W klasie Model utworzylibyśmy następujące zmienne: String height, String hair, String eyes.

Paradygmat 3. Enkapsulacja

Kiedyś już o tym mówiliśmy. Enkapsulacja w Javie oznacza ograniczenie możliwości odczytu i zmiany danych. Jak już zauważyłeś/aś, termin ten pochodzi od słowa „kapsuła”. Używamy tej „kapsuły”, aby uchronić pewne ważne dane przed dokonywaniem zmian przez innych. A oto prosty przykład z życia. Masz swoje imię i nazwisko. Znają je Twoi przyjaciele. Nie mają oni jednak możliwości zmiany ani Twojego imienia, ani nazwiska. Można by powiedzieć, że proces zmiany imienia został poddany „enkapsulacji” przez sąd: możesz je zmienić tylko za pośrednictwem urzędnika sądowego, ale możesz to zrobić tylko Ty. Dla innych „użytkowników” dostęp do Twojego imienia i nazwiska jest „tylko do odczytu” :) Kolejnym przykładem jest gotówka trzymana w domu. Położenie jej na widoku, np. na środku stołu, nie jest szczególnie dobrym pomysłem. Każdy „użytkownik” (ktoś, kto przyjdzie do Twojego domu) będzie mógł zmienić jej ilość, np. zabierając jej część. Lepiej by było poddać ją enkapsulacji. Czyli zrobić tak, żebyś tylko Ty miał/a do niej dostęp i to tylko pod warunkiem, że użyjesz specjalnego kodu. Oczywistymi przykładami enkapsulacji, z którymi już miałeś/aś do czynienia, są modyfikatory dostępu (private, public itp.), a także gettery i settery. Jeśli nie przeprowadzisz enkapsulacji pola age klasy Cat, każdy będzie mógł napisać:

Cat.age = -1000;
Enkapsulacji używamy, aby uchronić pole age przed możliwością wprowadzenia do niego ujemnej wartości przy użyciu metody setter.

Paradygmat 4. Polimorfizm

Polimorfizm daje możliwość pracy z wieloma typami tak, jakby były one tym samym typem. Co więcej, zachowanie każdego obiektu będzie się różniło w zależności od jego typu. Brzmi skomplikowanie? Uprośćmy to trochę. Najlepszy przykład: zwierzęta. Utwórz klasę Animal z pojedynczą metodą speak() i dwiema podklasami — Cat oraz Dog.

public class Animal {

   public void speak() {
      
       System.out.println("Hello!");
   }
}

public class Dog extends Animal {
  
   @Override
   public void speak() {
       System.out.println ("Woof-woof!");
   }
}

public class Cat extends Animal {

   @Override
   public void speak() {
       System.out.println("Meow!");
   }
}
A teraz zadeklarujmy zmienną referencyjną Animal i przypiszmy ją do obiektu Dog.

public class Main {

   public static void main(String[] args) {

       Animal dog = new Dog();
       dog.speak();
   }
}
Jak myślisz, jaka metoda zostanie wywołana? Animal.speak() czy Dog.speak()? Zostanie wywołana metoda w klasie Dog: Hau-hau! Utworzyliśmy referencję do Animal, ale obiekt zachowuje się jak Dog. Jeśli będzie to nam potrzebne, możemy stworzyć obiekty zachowujące się jak kot, koń czy każde inne stworzenie. Ważne jest, aby przypisać konkretną podklasę do bardziej ogólnej zmiennej referencyjnej Animal. Jest w tym sens, bo wszystkie psy to zwierzęta. Dokładnie to mieliśmy na myśli, mówiąc, że „zachowanie każdego obiektu będzie się różniło w zależności od jego typu”. Jeśli stworzymy obiekt Cat...

public static void main(String[] args) {

   Animal cat = new Cat();
   cat.speak();
}
metoda speak() wyświetli "Miau!" Co jednak znaczy „możliwość pracy z wieloma typami tak, jakby były one tym samym typem”? To też jest dość proste. Załóżmy, że chcemy stworzyć salon fryzjerski dla psów. W naszym salonie każde zwierzę powinno móc się ostrzyc, piszemy więc metodę trim() z parametrem Animal (zwierzę, które jest strzyżone).

public class AnimalBarbershop {

   public void trim(Animal animal) {

       System.out.println("The haircut is done!"); 
   }
}
A teraz możemy przekazać obiekty Cat i Dog do metody trim()!

public static void main(String[] args) {

   Cat cat = new Cat();
   Dog dog = new Dog();

   AnimalBarbershop barbershop = new AnimalBarbershop();

   barbershop.trim(cat);
   barbershop.trim(dog);
}
Prosty przykład: klasa PetGroomingSalon wchodzi w interakcję z typami Cat i Dog w taki sposób, jakby były one tym samym typem. Lecz jednocześnie zachowanie Cat i Dog jest różne, np. ich mowa jest inna. Paradygmaty programowania obiektowego - 2

Dlaczego potrzebujemy OOP?

Oraz jak to się stało, że OOP urosło do rangi paradygmatu w programowaniu? Programiści mieli przecież wcześniej funkcjonujące narzędzia, takie jak języki proceduralne. Co skłoniło ich do wynalezienia czegoś kompletnie nowego? Przede wszystkim była to złożoność zadań, z którymi mieli do czynienia. Jeśli 60 lat temu programista dostawał za zadanie „obliczyć jakieś wyrażenie matematyczne”, to obecnie moglibyśmy je przełożyć na: „zaimplementuj 7 różnych zakończeń dla gry S.T.A.L.K.E.R., w zależności od decyzji gracza w punktach A, B, C, D, E i F w tej grze”. Jak widzisz, w ostatnich dziesięcioleciach zadania stały się znacznie bardziej skomplikowane. Poskutkowało to większą złożonością typów danych. To kolejny powód, dla którego pojawiło się OOP. Wyrażenia matematyczne mogą zostać obliczone przy użyciu tylko zwyczajnych prymitywów. Nie są do tego potrzebne żadne obiekty. Ale zadanie z zakończeniami gry trudno by było nawet opisać bez użycia niestandardowych klas. Przy użyciu klas i obiektów można zatem zrobić to zadanie dość łatwo. Rzecz jasna, potrzebujemy kilku klas: Game, Stalker, Ending, PlayerDecision, GameEvent i tak dalej. Innymi słowy, nawet jeśli nie zaczniemy rozwiązywać tego problemu, łatwo możemy „naszkicować” sobie rozwiązanie w naszej głowie. Rosnąca złożoność zadań zmusiła programistów do rozkładania ich na części. A to wcale nie tak łatwo zrobić w programowaniu proceduralnym. Często bywało, że program wyglądał jak drzewo z całym mnóstwem gałęzi, reprezentującymi wszystkie możliwe sposoby wykonania. W zależności od różnych warunków wykonywana była taka czy inna gałąź programu. To działało nieźle dla małych programów, lecz duże programy trudno byłoby podzielić na części. Jest to kolejna przyczyna pojawienia się OOP. Nowy paradygmat dał programistom możliwość podzielenia programu na grupę „modułów” (klas), z których każdy wykonuje swoją część pracy. Dzięki ich wzajemnej interakcji wszystkie obiekty wykonują pracę naszego programu. Dodatkowo, możemy naszego kodu użyć ponownie w innym miejscu programu, co także zaoszczędza dużo czasu.
Ten artykuł przeczytasz także po angielsku.
Read the English version of this article for an introduction to OOP principles. The principles of object oriented programming form a solid foundation for your future career.
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION