CodeGym /Blog Java /Random-PL /Zasady OOP
Autor
Milan Vucic
Programming Tutor at Codementor.io

Zasady OOP

Opublikowano w grupie Random-PL
Java jest językiem zorientowanym obiektowo. Oznacza to, że musisz pisać programy Java przy użyciu paradygmatu zorientowanego obiektowo. A ten paradygmat pociąga za sobą używanie obiektów i klas w twoich programach. Spróbujmy na przykładach zrozumieć, czym są klasy i obiekty oraz jak stosować podstawowe zasady OOP (abstrakcja, dziedziczenie, polimorfizm i enkapsulacja) w praktyce.

Co to jest obiekt?

Świat, w którym żyjemy, składa się z przedmiotów. Rozglądając się, widzimy, że otaczają nas domy, drzewa, samochody, meble, naczynia i komputery. Wszystkie te rzeczy są przedmiotami, a każdy z nich ma zestaw określonych cech, zachowań i celów. Jesteśmy przyzwyczajeni do przedmiotów i zawsze używamy ich do bardzo konkretnych celów. Na przykład, jeśli musimy dostać się do pracy, korzystamy z samochodu. Jeśli chcemy jeść, używamy naczyń. A jeśli chcemy odpocząć, znajdziemy wygodną sofę. Ludzie są przyzwyczajeni do myślenia w kategoriach przedmiotów w celu rozwiązywania problemów w życiu codziennym. Jest to jeden z powodów, dla których obiekty są używane w programowaniu. Takie podejście nazywa się programowaniem obiektowym. Podajmy przykład. Wyobraź sobie, że opracowałeś nowy telefon i chcesz rozpocząć masową produkcję. Jako programista telefonu wiesz, do czego służy, jak to działa i jakie są jego części (korpus, mikrofon, głośnik, przewody, przyciski itp.). Co więcej, tylko Ty wiesz, jak połączyć te części. Ale nie planujesz robić telefonów osobiście — masz do tego cały zespół pracowników. Aby wyeliminować konieczność wielokrotnego wyjaśniania, jak połączyć części telefonu i upewnić się, że wszystkie telefony są wykonane w ten sam sposób, przed rozpoczęciem ich produkcji należy wykonać rysunek opisujący budowę telefonu. W OOP taki opis, rysunek, diagram lub szablon nazywamy klasą. Stanowi podstawę tworzenia obiektów podczas działania programu. Klasa to opis obiektów określonego typu — jak wspólny szablon składający się z pól, metod i konstruktora. Obiekt jest instancją klasy.

Abstrakcja

Pomyślmy teraz, jak możemy przejść od obiektu w świecie rzeczywistym do obiektu w programie. Użyjemy telefonu jako przykładu. Ten środek komunikacji ma ponad 100-letnią historię. Współczesny telefon jest znacznie bardziej złożonym urządzeniem niż jego XIX-wieczny poprzednik. Korzystając z telefonu nie myślimy o jego organizacji i zachodzących w nim procesach. Po prostu korzystamy z funkcji dostarczonych przez twórców telefonu: przycisków lub ekranu dotykowego do wpisania numeru telefonu i wykonywania połączeń. Jednym z pierwszych interfejsów telefonicznych była korba, którą trzeba było obrócić, aby wykonać połączenie. Oczywiście nie było to zbyt wygodne. Ale swoją funkcję spełniał bez zarzutu. Jeśli porównasz najnowocześniejsze i pierwsze telefony, można od razu zidentyfikować funkcje najważniejsze dla urządzenia z końca XIX wieku i dla współczesnego smartfona. Są to możliwość wykonywania połączeń i możliwość odbierania połączeń. W rzeczywistości to właśnie sprawia, że ​​telefon jest telefonem, a nie czymś innym. Teraz po prostu zastosowałem zasadę OOP: zidentyfikuj najważniejsze cechy i informacje o obiekcie. Ta zasada nazywa się abstrakcją. W OOP abstrakcję można również zdefiniować jako metodę reprezentacji elementów rzeczywistego zadania jako obiektów w programie. Abstrakcja zawsze wiąże się z uogólnieniem pewnych właściwości obiektu, dlatego najważniejsze jest oddzielenie informacji znaczących od nieistotnych w kontekście wykonywanego zadania. Dodatkowo może istnieć kilka poziomów abstrakcji. Pozwalać' spróbuj zastosować zasadę abstrakcji w naszych telefonach. Na początek zidentyfikujemy najpopularniejsze typy telefonów — od pierwszych po współczesne. Na przykład moglibyśmy przedstawić je w postaci diagramu na rysunku 1. Zasady OOP - 2Korzystając z abstrakcji, możemy teraz zidentyfikować ogólne informacje w tej hierarchii obiektów: ogólny obiekt abstrakcyjny (telefon), wspólne cechy telefonu (np. rok jego powstania) oraz wspólny interfejs (wszystkie telefony mogą odbierać i wykonywać połączenia). Oto jak to wygląda w Javie:

public abstract class AbstractPhone {
    private int year;

    public AbstractPhone(int year) {
        this.year = year;
    }
    public abstract void call(int outgoingNumber);
    public abstract void ring(int incomingNumber);
}
W programie możemy tworzyć nowe typy telefonów, używając tej abstrakcyjnej klasy i stosując inne podstawowe zasady OOP, które omówimy poniżej.

Kapsułkowanie

Za pomocą abstrakcji identyfikujemy to, co jest wspólne dla wszystkich obiektów. Ale każdy rodzaj telefonu jest wyjątkowy, w jakiś sposób różni się od innych. Jak w programie wyznaczamy granice i identyfikujemy tę indywidualność? Jak to zrobić, aby nikt przypadkowo lub celowo nie zepsuł nam telefonu ani nie próbował przerobić jednego modelu na inny? W prawdziwym świecie odpowiedź jest oczywista: musisz umieścić wszystkie części w etui na telefon. W końcu jeśli tego nie zrobisz — zamiast tego zostawisz wszystkie wewnętrzne części telefonu i przewody łączące na zewnątrz — jakiś ciekawski eksperymentator z pewnością będzie chciał „ulepszyć” nasz telefon. Aby zapobiec takim manipulacjom, w projektowaniu i działaniu obiektu stosowana jest zasada enkapsulacji. Zasada ta stwierdza, że ​​atrybuty i zachowanie obiektu są połączone w jedną klasę, obiekt wewnętrzna implementacja jest ukryta przed użytkownikiem, a do pracy z obiektem dostępny jest publiczny interfejs. Zadaniem programisty jest określenie, które atrybuty i metody obiektu powinny być dostępne publicznie, a które wewnętrzne szczegóły implementacji powinny być niedostępne.

Hermetyzacja i kontrola dostępu

Załóżmy, że informacja o telefonie (rok produkcji lub logo producenta) jest wygrawerowana na jego tylnej części w momencie jego produkcji. Informacja (jej stan) jest specyficzna dla tego konkretnego modelu. Można powiedzieć, że producent zadbał o niezmienność tych informacji — mało prawdopodobne, aby ktokolwiek pomyślał o usunięciu grawerunku. W świecie Javy klasa opisuje stan przyszłych obiektów za pomocą pól, a ich zachowanie za pomocą metod. Dostęp do stanu i zachowania obiektu jest kontrolowany za pomocą modyfikatorów stosowanych do pól i metod: private, protected, public i default. Na przykład zdecydowaliśmy, że rok produkcji, nazwa producenta i jedna z metod są wewnętrznymi szczegółami implementacji klasy i nie mogą być zmieniane przez inne obiekty w programie. W kodzie,

public class SomePhone {

    private int year;
    private String company;
    public SomePhone(int year, String company) {
        this.year = year;
        this.company = company;
    }
private void openConnection(){
    // findSwitch
    // openNewConnection...
}
public void call() {
    openConnection();
    System.out.println("Calling");
}

public void ring() {
    System.out.println("Ring-ring");
}

 }
Modyfikator private umożliwia dostęp do pól i metod klasy tylko w obrębie tej klasy. Oznacza to, że dostęp do pól prywatnych z zewnątrz jest niemożliwy, ponieważ nie można wywołać metod prywatnych. Ograniczenie dostępu do metody openConnection pozostawia nam również możliwość dowolnej zmiany wewnętrznej implementacji metody, ponieważ gwarantujemy, że metoda nie będzie używana przez inne obiekty ani nie przerywa pracy innych obiektów. Aby pracować z naszym obiektem, pozostawiamy metody call i ring dostępne przy użyciu modyfikatora public. Zapewnienie publicznych metod pracy z obiektami jest również częścią enkapsulacji, ponieważ gdyby całkowicie odmówić dostępu, stałoby się to bezużyteczne.

Dziedzictwo

Przyjrzyjmy się jeszcze raz schematowi telefonów. Widać, że jest to hierarchia, w której model ma wszystkie cechy modeli znajdujących się wyżej w swojej gałęzi i dodaje kilka własnych. Na przykład smartfon wykorzystuje do komunikacji sieć komórkową (ma właściwości telefonu komórkowego), jest bezprzewodowy i przenośny (ma właściwości telefonu bezprzewodowego) oraz może odbierać i wykonywać połączenia (ma właściwości telefonu). Mamy tu do czynienia z dziedziczeniem właściwości obiektu. W programowaniu dziedziczenie oznacza użycie istniejących klas do zdefiniowania nowych. Rozważmy przykład wykorzystania dziedziczenia do utworzenia klasy smartfona. Wszystkie telefony bezprzewodowe są zasilane akumulatorami, które mają określoną żywotność. W związku z tym dodajemy tę właściwość do klasy telefonów bezprzewodowych:

public abstract class CordlessPhone extends AbstractPhone {

    private int hour;

    public CordlessPhone (int year, int hour) {
        super(year);
        this.hour = hour;
    }
    }
Telefony komórkowe dziedziczą właściwości telefonu bezprzewodowego, aw tej klasie implementujemy metody dzwonienia i dzwonienia :

public class CellPhone extends CordlessPhone {
    public CellPhone(int year, int hour) {
        super(year, hour);
    }

    @Override
    public void call(int outgoingNumber) {
        System.out.println("Calling " + outgoingNumber);
    }

    @Override
    public void ring(int incomingNumber) {
        System.out.println("Incoming call from " + incomingNumber);
    }
}
I wreszcie mamy klasę smartfonów, która w przeciwieństwie do klasycznych telefonów komórkowych ma pełnoprawny system operacyjny. Możesz rozszerzyć funkcjonalność swojego smartfona, dodając nowe programy, które mogą działać w jego systemie operacyjnym. W kodzie klasę można opisać w następujący sposób:

public class Smartphone extends CellPhone {
    
    private String operationSystem;

    public Smartphone(int year, int hour, String operationSystem) {
        super(year, hour);
        this.operationSystem = operationSystem;
    }
public void install(String program) {
    System.out.println("Installing " + program + " for " + operationSystem);
}

}
Jak widać, stworzyliśmy całkiem sporo nowego kodu opisującego klasę Smartphone , ale otrzymaliśmy nową klasę z nową funkcjonalnością. Ta zasada OOP pozwala znacznie zmniejszyć ilość wymaganego kodu Java, ułatwiając tym samym życie programiście.

Wielopostaciowość

Pomimo różnic w wyglądzie i konstrukcji różnych rodzajów telefonów, możemy zidentyfikować pewne wspólne zachowanie: wszystkie mogą odbierać i wykonywać połączenia oraz wszystkie mają dość przejrzysty i prosty zestaw elementów sterujących. Od strony programistycznej zasada abstrakcji (którą już znamy) pozwala nam powiedzieć, że obiekty telefoniczne mają wspólny interfejs. Dlatego ludzie mogą z łatwością korzystać z różnych modeli telefonów, które mają takie same elementy sterujące (przyciski mechaniczne lub ekran dotykowy), bez zagłębiania się w szczegóły techniczne urządzenia. W ten sposób stale korzystasz z telefonu komórkowego i możesz łatwo wykonać połączenie z telefonu stacjonarnego znajomego. Zasada OOP, która mówi, że program może używać obiektów ze wspólnym interfejsem bez żadnych informacji o wewnętrznej strukturze obiektu, nazywa się polimorfizmem. Pozwalać' Wyobraźmy sobie, że potrzebujemy naszego programu do opisania użytkownika, który może użyć dowolnego telefonu, aby zadzwonić do innego użytkownika. Oto jak możemy to zrobić:

public class User {
    private String name;

    public User(String name) {
        this.name = name;
            }

    public void callAnotherUser(int number, AbstractPhone phone){
// And here's polymorphism: using the AbstractPhone type in the code!
        phone.call(number);
    }
}
 }
Teraz opiszemy kilka rodzajów telefonów. Jeden z pierwszych telefonów:

public class ThomasEdisonPhone extends AbstractPhone {

public ThomasEdisonPhone(int year) {
    super(year);
}
    @Override
    public void call(int outgoingNumber) {
        System.out.println("Crank the handle");
        System.out.println("What number would you like to connect to?");
    }

    @Override
    public void ring(int incomingNumber) {
        System.out.println("The phone is ringing");
    }
}
Zwykły telefon stacjonarny:

public class Phone extends AbstractPhone {

    public Phone(int year) {
        super(year);
    }

    @Override
    public void call(int outgoingNumber) {
        System.out.println("Calling " + outgoingNumber);
    }

    @Override
    public void ring(int incomingNumber) {
        System.out.println("The phone is ringing");
    }
}
I wreszcie fajny telefon wideo:

public class VideoPhone extends AbstractPhone {

    public VideoPhone(int year) {
        super(year);
    }
    @Override
    public void call(int outgoingNumber) {
        System.out.println("Connecting video call to " + outgoingNumber);
    }
    @Override
    public void ring(int incomingNumber) {
        System.out.println("Incoming video call from " + incomingNumber);
    }
  }
Stworzymy obiekty w metodzie main() i przetestujemy metodę callAnotherUser() :

AbstractPhone firstPhone = new ThomasEdisonPhone(1879);
AbstractPhone phone = new Phone(1984);
AbstractPhone videoPhone=new VideoPhone(2018);
User user = new User("Jason");
user.callAnotherUser(224466, firstPhone);
// Crank the handle
// What number would you like to connect to?
user.callAnotherUser(224466, phone);
// Calling 224466
user.callAnotherUser(224466, videoPhone);
// Connecting video call to 224466
Wywołanie tej samej metody na obiekcie użytkownika daje różne wyniki. Konkretna implementacja metody call jest wybierana dynamicznie wewnątrz metody callAnotherUser() na podstawie określonego typu obiektu przekazywanego podczas działania programu. To główna zaleta polimorfizmu – możliwość wyboru implementacji w czasie wykonywania. W podanych powyżej przykładach klas telefonów zastosowaliśmy nadpisywanie metod — trik polegający na zmianie implementacji metody zdefiniowanej w klasie bazowej bez zmiany sygnatury metody. Zasadniczo zastępuje to metodę: nowa metoda zdefiniowana w podklasie jest wywoływana podczas wykonywania programu. Zwykle, gdy nadpisujemy metodę, @Overrideużywana jest adnotacja. Mówi kompilatorowi, aby sprawdził podpisy nadpisanych i nadpisanych metod. Na koniec, aby upewnić się, że Twoje programy Java są zgodne z zasadami OOP, postępuj zgodnie z tymi wskazówkami:
  • zidentyfikować główne cechy obiektu;
  • identyfikować wspólne właściwości i zachowania oraz wykorzystywać dziedziczenie podczas tworzenia klas;
  • używać typów abstrakcyjnych do opisywania obiektów;
  • staraj się zawsze ukrywać metody i pola związane z wewnętrzną implementacją klasy.
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION