CodeGym/Blog Java/Random-PL/Koncepcje OOP w Javie
Autor
Alex Vypirailenko
Java Developer at Toshiba Global Commerce Solutions

Koncepcje OOP w Javie

Opublikowano w grupie Random-PL
Jedną z największych zalet Javy jest programowanie obiektowe (OOP). To jest powód, dla którego ten język stał się tak popularny i dobrze nadaje się do projektów dowolnej wielkości. Co to jest programowanie obiektowe? To nie magia, ale może wydawać się magiczna, jeśli naprawdę się w to zagłębisz. OOP dotyczy tego, jak zbudować oprogramowanie. Jest to koncepcja, a raczej zbiór koncepcji oop w Javie, które pozwalają tworzyć określone interakcje i relacje między obiektami Java w celu efektywnego tworzenia i używania oprogramowania. Koncepcje OOP w Javie - 1Klasyczny OOP obejmuje 3 + 1 główne koncepcje. Zacznijmy od klasyki.

Obiekt

Obiekty Java, podobnie jak obiekty świata rzeczywistego, mają dwie cechy: stan i zachowanie.

Na przykład obiekt ludzki ma stan (imię, płeć, spanie lub nie…) i zachowanie (studiuje Javę, spacery, rozmowy…). Każdy obiekt Java przechowuje swój stan w polach i ujawnia swoje zachowanie za pomocą metod.

Kapsułkowanie

Hermetyzacja danych polega na ukrywaniu danych wewnętrznych przed światem zewnętrznym i uzyskiwaniu do nich dostępu wyłącznie za pomocą publicznie ujawnionych metod. Co to znaczy? Jakie dane? Ukrywanie się przed kim? Ukrywanie oznacza ograniczenie bezpośredniego dostępu do członków danych (pól) klasy.

Jak to działa w Javie:

  1. Pola są prywatne
  2. Każde pole w klasie otrzymuje dwie specjalne metody: getter i setter. Metody pobierające zwracają wartość pola. Metody ustawiające pozwalają na zmianę wartości pola w sposób pośredni, ale dozwolony.

Przykład enkapsulacji w kodzie Java:

public class Student {
private int age;
private String name;

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

public class Test{
public static void main(String[] args) {
Student firstStudent = new Student();
firstStudent.setName("John");
// The name field is private, so you can no longer do this:  firstStudent.name = "John";
}
}

Dlaczego warto stosować enkapsulację?

Głównym powodem jest ułatwienie zmiany kodu. Wyobraź sobie, że masz aplikację do szkoły hokejowej i istnieje klasa HockeyStudent z dwoma polami, w których przechowywane jest imię i nazwisko ucznia oraz wiek, kiedy zapisał się do szkoły. Coś takiego:
public class HockeyStudent {
public String name;
public  int ageOfEnrollment;
}
Pole ageOfEnrollment jest publiczne, żadnych getterów ani setterów… Ta klasa jest używana przez wiele innych klas i wszystko było ok, dopóki jakiś programista nie stwierdził, że jedno pole int to za mało. Niektórzy hokeiści w kohorcie są prawie o rok starsi od swoich rówieśników, więc wygodniej byłoby podzielić ich na dwie grupy w zależności od miesiąca urodzenia. Zatem pole ageOfEnrollment należy zmienić na tablicę int (int[][]) : pierwsza liczba oznacza pełne lata, a druga miesiące. Teraz musisz zrefaktoryzować cały kod, który używa klasy Student ! Ale jeśli twój ageOfEnrollmentpole jest prywatne i masz gettery i settery, wtedy wszystko jest łatwiejsze. Jeśli zmieni się wymóg ustawienia wieku ucznia, po prostu zaktualizuj logikę w metodzie ustawiającej setAgeOfEnrollment() , a Twoje klasy mogą bez problemu kontynuować korzystanie ze Studenta ! Ten przykład jest nieco wymyślony, ale mam nadzieję, że wyjaśnia, dlaczego użycie enkapsulacji jest świetnym pomysłem.

Dziedzictwo

Zasada ta jest łatwiejsza do zrozumienia nawet bez praktycznego doświadczenia. Nie powtarzaj się (DRY) może być mottem koncepcji dziedziczenia. Dziedziczenie umożliwia utworzenie klasy potomnej, która dziedziczy pola i metody klasy nadrzędnej bez ponownego ich definiowania. Jasne, możesz zastąpić pola i metody klasy nadrzędnej w klasie podrzędnej, ale nie jest to konieczne. Co więcej, możesz dodawać nowe stany i zachowania w klasie potomnej. Klasy nadrzędne są czasami nazywane klasami nadrzędnymi lub klasami podstawowymi, a klasy podrzędne nazywane są podklasami. Słowo kluczowe extends języka Java służy do implementacji zasady dziedziczenia w kodzie.

Jak to działa w Javie:

  1. Utwórz klasę nadrzędną.
  2. Utwórz klasę potomną za pomocą słowa kluczowego extends .
  3. W konstruktorze klasy Child użyj metody super(parentField1, parentField2, ...) , aby ustawić pola rodzica.

Konstruktor to specjalna metoda używana do inicjalizacji nowo utworzonego obiektu. Konstruktor ma taką samą nazwę jak nazwa jego klasy. Istnieją dwa typy konstruktorów: domyślny (konstruktor bezargumentowy) i konstruktor sparametryzowany. Klasa musi mieć co najmniej jednego konstruktora (ma konstruktora domyślnego, jeśli nie zdefiniowano innych konstruktorów) i może mieć ich wiele.

Za każdym razem, gdy tworzysz nowy obiekt, wywołujesz jego konstruktora. W powyższym przykładzie robisz to w tym wierszu:

Student firstStudent = new Student();

Używasz słowa kluczowego new do wywołania domyślnego konstruktora klasy Student : tudent() .

Niektóre zasady:

  1. Jedna klasa może mieć tylko jednego rodzica.
  2. Jedna klasa nadrzędna może mieć wiele klas podrzędnych.
  3. Klasa podrzędna może mieć własne klasy podrzędne.

Przykład dziedziczenia w kodzie Java

Stwórzmy klasę Telefon .
public class Phone {
    int price;
    double weight;

// Constructor
public Phone(int price, double weight) {
        this.price = price;
        this.weight = weight;
    }

    void orderPhone(){
        System.out.println("Ordering phone...");
    }
}
Oczywiście istnieją różne typy telefonów, więc utwórzmy dwie klasy podrzędne: jedną dla telefonów z systemem Android i drugą dla iPhone'ów. Następnie dodamy kilka pól i metod, których nie ma element nadrzędny. Użyjemy super() do wywołania konstruktorów w celu zainicjowania pól, które ma klasa nadrzędna.

Przykład dziedziczenia w Javie

public class Android extends Phone {

// Some new fields
String androidVersion;
int screenSize;

    String secretDeviceCode;

// Constructor
    public Android(int price, double weight, String androidVersion, int screenSize, String secretDeviceCode) {
        super(price, weight); // Android inherits Phone’s fields

        //this - reference to the current object
        //super - reference to the parent object

        this.androidVersion = androidVersion;
        this.screenSize = screenSize;
        this.secretDeviceCode = secretDeviceCode;
    }

	// New Android-specific method, does not exist in the Phone class
    void installNewAndroidVersion() {
        System.out.println("installNewAndroidVersion invoked...");

    }

}

public class IPhone extends Phone {

    boolean fingerPrint;

    public IPhone(int price, double weight, boolean fingerPrint) {
        super(price, weight);
        System.out.println("IPhone constructor was invoked...");
        this.fingerPrint = fingerPrint;
    }

    void deleteIPhoneFromDb() {
        System.out.println("deleteIPhoneFromDb invoked...");
    }

@Override // This is about polymorphism, see below
void orderPhone(){
        System.out.println("Ordering my new iPhone and deleting the old one...");
    }
}
Powtarzam więc: w Javie dziedziczenie pozwala rozszerzyć klasę o klasy potomne, które dziedziczą pola i metody klasy nadrzędnej. To doskonały sposób na osiągnięcie możliwości ponownego wykorzystania kodu.

Wielopostaciowość

Polimorfizm to zdolność obiektu do morfowania, przybierania różnych form lub raczej działania na różne sposoby. W Javie polimorfizm zwykle występuje, gdy odwołanie do klasy nadrzędnej jest używane w odniesieniu do obiektu klasy podrzędnej.

Co to oznacza i jak to działa w Javie:

Czym jest polimorfizm w Javie? Ogólnie oznacza to, że możesz używać tej samej nazwy metody do różnych celów. Istnieją dwa rodzaje polimorfizmu w Javie: nadpisywanie metod (polimorfizm dynamiczny) i przeciążanie metod (polimorfizm statyczny).

Przesłanianie metody

Możesz zastąpić metodę klasy nadrzędnej w klasie podrzędnej, zmuszając ją do działania w inny sposób. Stwórzmy klasę nadrzędną Musician za pomocą metody play() .

Przykład polimorfizmu w kodzie Java

public class Musician {
    String name;
    int age;

    // Default constructor
    public Musician() {
    }

    // Parameterized constructor
    public Musician(String name, int age) {
        this.name = name;
        this.age = age;
    }

    void play() {
        System.out.println("I am playing my instrument...");
    }
}
Różni muzycy używają różnych instrumentów. Utwórzmy dwie klasy podrzędne: Pianist i Violinist . Dzięki polimorfizmowi każdy wykona własną wersję metody play() . Podczas zastępowania możesz użyć adnotacji @Override , ale nie jest to konieczne.
public class Pianist extends Musician {

    String favoritePianoType;

    public Pianist(String name, int age, String favoritePianoType) {
        super(name, age);
        this.favoritePianoType = favoritePianoType;
    }


    @Override
void play(){
        System.out.println("I am playing the piano...");
    }
}
Skrzypek może być solistą lub członkiem orkiestry. Weźmy to pod uwagę podczas nadpisywania naszej metody play() .
public class Violinist extends Musician {
    boolean isSoloist;

public Violinist(String name, int age, boolean isSoloist) {
            super(name, age);
            this.isSoloist = isSoloist;
        }


    @Override
void play(){
if (isSoloist)
        System.out.println("I am playing the violin solo...");
else
System.out.println("I am playing the violin in an orchestra...");

    }
}
Stwórzmy klasę Demo , w której utworzymy trzy obiekty, po jednym egzemplarzu każdej z wcześniej utworzonych klas. Zobaczymy jakie wyniki uzyskamy.
public class Demo {
  public static void main(String[] args) {
  Musician musician = new Musician();
  Violinist violinist = new Violinist("John", 32, true);
  Pianist pianist = new Pianist("Glen", 30, "Acoustic");

  System.out.println("Musician said:");
  musician.play();
  System.out.println("Violinist said:");
  violinist.play();
  System.out.println("Pianist said:");
  pianist.play();
    }
}
Oto, co otrzymujemy:
Musician said:
I am playing my instrument...
Violinist said:
I am playing the violin solo…
Pianist said:
I am playing the piano...
Każdy skrzypek i pianista jest muzykiem, ale nie każdy muzyk jest altowiolistą czy pianistą. Oznacza to, że możesz użyć metody gry muzyka, jeśli nie musisz tworzyć nowej. Lub możesz wywołać metodę rodzica od dziecka, używając super słowa kluczowego. Zróbmy to w kodzie Pianisty:
public class Pianist extends Musician {

    String favoritePianoType;

    @Override
    void play(){
        super.play();
        System.out.println("I am playing the piano...");
    }
}
Teraz wywołajmy naszą metodę main() w klasie Demo . Oto wynik:
Musician said:
I am playing my instrument...
Violinist said:
I am playing the violin solo...
Pianist said:
I am playing my instrument...
I am playing the piano...

Przeciążenie metody

Przeciążanie metod oznacza używanie różnych metod o tej samej nazwie w tej samej klasie. Muszą różnić się liczbą, kolejnością lub rodzajem parametrów. Załóżmy, że pianista potrafi grać na pianinie akustycznym i pianinie elektrycznym. Aby grać na elektryce, muzyk potrzebuje elektryczności. Stwórzmy dwie różne metody play() . Pierwsza bez parametrów, na pianino akustyczne, a druga z parametrem informującym o dostępności prądu.
public class Pianist extends Musician {

    String name;
    int age;
    String favoritePianoType;

    @Override
    void play(){
        super.play();
        System.out.println("I am playing the piano...");
    }
    void play(boolean isElectricity){
        if (isElectricity) {
            System.out.println("The electricity is on.");
            System.out.println("I am playing the piano...");
        }
        else System.out.println("I can't play this without electricity.");
    }
}
Nawiasem mówiąc, możesz użyć pierwszej metody play() wewnątrz drugiej metody play(boolean) w następujący sposób:
void play(boolean isElectricity){
        if (isElectricity) {
            System.out.println("The electricity is on.");
            play();
        }
        else System.out.println("I can't play this without electricity.");
    }
Dodajmy kilka wierszy do naszej klasy Demo , aby zademonstrować nasze przeciążenie:
public class Demo {
    public static void main(String[] args) {

        Musician musician = new Musician();
        Violinist violinist = new Violinist("John", 23, true);
        Pianist pianist = new Pianist("Glen", 30, "Acoustic");

        System.out.println("Musician said:");
        musician.play();
        System.out.println("Violinist said:");
        violinist.play();
        System.out.println("Pianist said:");
        pianist.play();
        System.out.println("The pianist will now try the electric piano:");
        pianist.play(true);
        System.out.println("The electricity has been shut off. Now when trying the electric piano, the pianist says:");
        pianist.play(false);
    }
}
Oto wynik:
Musician said:
I am playing my instrument...
Violinist said:
I am playing the violin solo...
Pianist said:
I am playing my instrument...
I am playing the piano...
The pianist will now try the electric piano:
The electricity is on.
I am playing my instrument...
I am playing the piano...
The electricity has been shut off. Now when trying the electric piano, the pianist says:
I can't play this without electricity.
Java wie, której metody należy użyć na podstawie jej parametrów i typu obiektu. To jest polimorfizm.

Abstrakcja

Kiedy definiujemy klasę, próbujemy zbudować model czegoś. Załóżmy na przykład, że piszemy grę wideo o nazwie MyRacer z różnymi samochodami wyścigowymi. Gracz może wybrać jedną z nich, a później ją zaktualizować lub kupić inną. Więc… Czym jest samochód? Samochód to dość skomplikowana rzecz, ale jeśli próbujemy stworzyć wyścigową grę wideo (w przeciwieństwie do symulatora jazdy), to nie musimy opisywać wszystkich tysięcy kół zębatych i uszczelek, które zawiera. Potrzebujemy jego modelu, prędkości maksymalnej, właściwości manewrowych, ceny, koloru… I może to wystarczy. To model samochodu do naszej gry. Załóżmy, że później w MyRacer 2 zdecydujemy się dodać opony, które wpływają na prowadzenie na drodze. Tutaj model jest inny, ponieważ dodaliśmy więcej szczegółów. Pozwalać' definiują abstrakcję danych jako proces identyfikowania tylko ważnych (lub niezbędnych) cech obiektu i ignorowania wszelkich nieistotnych szczegółów. Istnieją różne poziomy abstrakcji. Na przykład, jeśli jesteś pasażerem autobusu, musisz wiedzieć, jak wygląda Twój autobus i dokąd jedzie, ale nie musisz wiedzieć, jak nim prowadzić. Jeśli jesteś kierowcą autobusu, nie musisz wiedzieć, jak stworzyć nowy autobus — wystarczy, że wiesz, jak go prowadzić. Ale jeśli jesteś producentem autobusów, musisz zejść na niższy poziom abstrakcji, ponieważ szczegóły projektu autobusu są dla Ciebie bardzo ważne. Mam nadzieję, że rozumiesz co mam na myśli. musisz wiedzieć, jak wygląda twój autobus i dokąd jedzie, ale nie musisz wiedzieć, jak go prowadzić. Jeśli jesteś kierowcą autobusu, nie musisz wiedzieć, jak stworzyć nowy autobus — wystarczy, że wiesz, jak go prowadzić. Ale jeśli jesteś producentem autobusów, musisz zejść na niższy poziom abstrakcji, ponieważ szczegóły projektu autobusu są dla Ciebie bardzo ważne. Mam nadzieję, że rozumiesz co mam na myśli. musisz wiedzieć, jak wygląda twój autobus i dokąd jedzie, ale nie musisz wiedzieć, jak go prowadzić. Jeśli jesteś kierowcą autobusu, nie musisz wiedzieć, jak stworzyć nowy autobus — wystarczy, że wiesz, jak go prowadzić. Ale jeśli jesteś producentem autobusów, musisz zejść na niższy poziom abstrakcji, ponieważ szczegóły projektu autobusu są dla Ciebie bardzo ważne. Mam nadzieję, że rozumiesz co mam na myśli.

Jak to działa w Javie:

Zbudujmy cztery poziomy abstrakcji w Javie, a raczej w OOP — od najniższego (najbardziej szczegółowego) do najwyższego (najbardziej abstrakcyjnego).
  1. Najniższy poziom abstrakcji to konkretny obiekt. Jest bytem o zbiorze cech, które należą do określonej klasy. Ma określone wartości pól

  2. Szablonem do tworzenia obiektów jest klasa. Jest to opis zbioru obiektów o podobnych właściwościach i strukturze wewnętrznej.

  3. Klasa abstrakcyjna to abstrakcyjny opis cech zestawu klas (działa jako szablon do dziedziczenia przez inne klasy). Ma wysoki poziom abstrakcji, więc nie jest możliwe tworzenie obiektów bezpośrednio z klasy abstrakcyjnej. Do tworzenia obiektów można używać tylko klas potomnych klas abstrakcyjnych. Klasa abstrakcyjna może zawierać metody z implementacją, ale nie jest to wymagane.

  4. Interfejs to konstrukcja języka programowania Java, która zawiera tylko abstrakcyjne metody publiczne i stałe pola statyczne (final static). Innymi słowy, ani klasy abstrakcyjne, ani interfejsy nie mogą być używane do generowania obiektów.

BTW, w Javie 8 lub nowszej interfejsy mogą mieć nie tylko abstrakcyjne metody i stałe, ale także metody domyślne i statyczne. W Javie interfejs definiuje zachowanie, podczas gdy klasa abstrakcyjna służy do tworzenia hierarchii. Jeden interfejs może być zaimplementowany przez wiele klas.

Przykład interfejsu w kodzie Java

interface Human {
	public void struggle();
	public void protect();
}

interface Vulcan {
	int angleOfPointyEars;
	public void turnOffEmotions(boolean isOn);
	public void telepathy();
}
Możesz zaimplementować więcej niż jeden interfejs
The Spock class implements Human and Vulcan {
public void struggle() {
System.out.println("I am struggling...");
}
	public void protect() {
System.out.println("You are under my protection!);
}
public void turnOffEmotions(boolean isOn){
If (isOn) {
System.out.println("I am turning off my emotions.");
isOn= !isOn;
}
}
	public void telepathy() {
System.out.println("Connecting to your brain...");
}

}
Dla początkujących studentów obejmuje to wszystkie główne koncepcje programowania obiektowego w Javie. Oprócz 4 głównych zasad OOP, Java ma również asocjację, agregację i kompozycję. Można je nazwać „dodatkowymi zasadami OOP”. Zasługują na osobny artykuł.
Komentarze (1)
  • Popularne
  • Najnowsze
  • Najstarsze
Musisz się zalogować, aby dodać komentarz
Przemo
Poziom 15 , Legnica, Poland
18 stycznia, 12:07
W abstrakcji dużo błędów i tekst się powtarza trzy razy.