CodeGym /Java Blog /Random-IT /Concetti OOP in Java
John Squirrels
Livello 41
San Francisco

Concetti OOP in Java

Pubblicato nel gruppo Random-IT
Uno dei maggiori punti di forza di Java è la programmazione orientata agli oggetti (OOP). Questo è il motivo per cui questo linguaggio è diventato così popolare ed è adatto a progetti di qualsiasi dimensione. Cos'è la programmazione orientata agli oggetti? Non è magia, ma può sembrare magica se ci entri davvero. OOP riguarda come costruire il tuo software. È un concetto, o meglio un insieme di concetti oop in Java, che consentono di creare alcune interazioni e relazioni specifiche tra oggetti Java al fine di sviluppare e utilizzare in modo efficace il software. Concetti OOP in Java - 1L'OOP classico include 3 + 1 concetti principali. Partiamo dai classici.

L'oggetto

Gli oggetti Java così come gli oggetti del mondo reale hanno due caratteristiche: stato e comportamento.

Ad esempio, un oggetto Umano ha uno stato (nome, sesso, dormire o no...) e un comportamento (studia Java, cammina, parla...). Qualsiasi oggetto Java memorizza il suo stato nei campi ed espone il suo comportamento attraverso i metodi.

Incapsulamento

L'incapsulamento dei dati nasconde i dati interni dal mondo esterno e vi accede solo tramite metodi esposti pubblicamente. Che cosa significa? Quali dati? Nascondersi da chi? Nascondere significa limitare l'accesso diretto ai membri dati (campi) di una classe.

Come funziona in Java:

  1. I campi sono resi privati
  2. Ogni campo in una classe ottiene due metodi speciali: un getter e un setter. I metodi getter restituiscono il valore del campo. I metodi setter consentono di modificare il valore del campo in modo indiretto ma consentito.

Esempio di incapsulamento in codice 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"; 
}
}

Perché dovresti usare l'incapsulamento?

Il motivo principale è semplificare la modifica del codice. Immagina di avere una domanda per una scuola di hockey e che ci sia una classe HockeyStudent con due campi che memorizzano il nome e l'età dello studente quando si è iscritto alla scuola. Qualcosa come questo:

public class HockeyStudent {
public String name;
public  int ageOfEnrollment;
}
Il campo ageOfEnrollment è pubblico, nessun getter o setter... Questa classe è utilizzata da molte altre classi e tutto andava bene fino a quando alcuni sviluppatori non hanno deciso che un singolo campo int non era sufficiente. Alcuni giocatori di hockey in una coorte hanno quasi un anno in più dei loro coetanei, quindi sarebbe più conveniente dividerli in due gruppi a seconda del mese in cui sono nati. Quindi il campo ageOfEnrollment dovrebbe essere modificato in un array int (int[][]) : il primo numero è per gli anni interi e il secondo è per i mesi. Ora devi eseguire il refactoring di tutto il codice che utilizza la classe Student ! Ma se la tua ageOfEnrollmentfield è privato e hai getter e setter, quindi tutto è più semplice. Se il requisito per l'impostazione dell'età di uno studente cambia, è sufficiente aggiornare la logica nel metodo setter setAgeOfEnrollment() e le tue classi possono continuare a utilizzare Student senza problemi! Questo esempio è in qualche modo artificioso, ma spero che spieghi perché usare l'incapsulamento è una grande idea.

Eredità

Questo principio è più facile da capire anche senza alcuna esperienza pratica. Non ripeterti (DRY) potrebbe essere il motto per il concetto di eredità. L'ereditarietà consente di creare una classe figlio che eredita i campi e i metodi della classe genitore senza ridefinirli. Certo, puoi sovrascrivere i campi e i metodi della classe genitore nella classe figlia, ma non è una necessità. Inoltre, puoi aggiungere nuovi stati e comportamenti nella classe figlio. Le classi padre sono talvolta chiamate superclassi o classi base e le classi figlie sono note come sottoclassi. La parola chiave extends di Java viene utilizzata per implementare il principio di ereditarietà nel codice.

Come funziona in Java:

  1. Crea la classe genitore.
  2. Crea la classe figlia usando la parola chiave extends .
  3. Nel costruttore della classe Child utilizzare il metodo super(parentField1, parentField2, ...) per impostare i campi del genitore.

Un costruttore è un metodo speciale utilizzato per inizializzare un oggetto appena creato. Un costruttore ha lo stesso nome del nome della sua classe. Esistono due tipi di costruttori: default (costruttore no-arg) e costruttore parametrizzato. Una classe deve avere almeno un costruttore (ha il costruttore di default se non sono stati definiti altri costruttori) e può averne molti.

Ogni volta che crei un nuovo oggetto, chiami il suo costruttore. Nell'esempio sopra, lo fai in questa riga:


Student firstStudent = new Student();

Si utilizza la nuova parola chiave per chiamare il costruttore predefinito della classe Student : tudent() .

Alcune regole:

  1. Una classe può avere un solo genitore.
  2. Una classe genitore può avere molte classi figlie.
  3. Una classe figlia può avere le proprie classi figlie.

Esempio di ereditarietà nel codice Java

Creiamo una classe Telefono .

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...");
    }
}
Ovviamente esistono diversi tipi di telefoni, quindi creiamo due classi figlio: una per i telefoni Android e una seconda per gli iPhone. Quindi aggiungeremo alcuni campi e metodi che il genitore non ha. E useremo super() per chiamare i costruttori per inizializzare i campi che la classe genitore ha.

Esempio di ereditarietà in Java


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...");
    }
}
Quindi, per ripetere: in Java, l'ereditarietà consente di estendere una classe con classi figlie che ereditano i campi ei metodi della classe genitore. È un modo eccellente per ottenere la riusabilità del codice.

Polimorfismo

Il polimorfismo è la capacità di un oggetto di trasformarsi, assumendo forme diverse o piuttosto agendo in modi diversi. In Java, il polimorfismo di solito si verifica quando un riferimento alla classe genitore viene utilizzato per fare riferimento a un oggetto della classe figlia.

Cosa significa e come funziona in Java:

Cos'è il polimorfismo in Java? In generale, ciò significa che è possibile utilizzare lo stesso nome di metodo per scopi diversi. Esistono due tipi di polimorfismo in Java: override del metodo (polimorfismo dinamico) e overload del metodo (polimorfismo statico).

Override del metodo

Puoi sovrascrivere il metodo di una classe genitore in una classe figlia, costringendolo a funzionare in modo diverso. Creiamo una classe genitore Musician con un metodo play() .

Esempio di polimorfismo nel codice 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...");
    }
}
Musicisti diversi usano strumenti diversi. Creiamo due classi child: Pianist e Violinist . Grazie al polimorfismo, ognuno eseguirà la propria versione del metodo play() . Quando si esegue l'override, è possibile utilizzare l' annotazione @Override , ma non è necessario.

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...");
    }
}
Il violinista potrebbe essere un solista o un membro di un'orchestra. Prendiamolo in considerazione quando sovrascriviamo il nostro metodo 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...");

    }
}
Creiamo una classe Demo , in cui creeremo tre oggetti, un'istanza di ciascuna delle classi precedentemente create. Vedremo che risultati otterremo.

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();
    }
}
Ecco cosa otteniamo:

Musician said:
I am playing my instrument...
Violinist said:
I am playing the violin solo…
Pianist said:
I am playing the piano...
Ogni violinista e pianista è un musicista, ma non tutti i musicisti sono violisti o pianisti. Ciò significa che puoi usare il metodo di gioco del musicista se non hai bisogno di crearne uno nuovo. Oppure puoi chiamare il metodo del genitore dal figlio usando la parola chiave super . Facciamolo nel codice di Pianist:

public class Pianist extends Musician {

    String favoritePianoType;
    
    @Override
    void play(){
        super.play();
        System.out.println("I am playing the piano...");
    }
}
Ora chiamiamo il nostro metodo main() nella classe Demo . Ecco il risultato:

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...

Sovraccarico del metodo

Sovraccaricare il metodo significa utilizzare vari metodi con lo stesso nome nella stessa classe. Devono essere diversi in termini di numero, ordine o tipi dei loro parametri. Supponiamo che un pianista sappia suonare un pianoforte acustico e uno elettrico. Per suonare un elettrico, il musicista ha bisogno di elettricità. Creiamo due diversi metodi play() . La prima senza parametri, per un pianoforte acustico, e la seconda con un parametro che indica se l'elettricità è disponibile.

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.");
    }
}
A proposito, puoi usare il primo metodo play() all'interno del secondo metodo play(boolean) in questo modo:

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.");
    }
Aggiungiamo alcune righe alla nostra classe Demo per dimostrare il nostro sovraccarico:

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);
    }
}
Ecco il risultato:

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 sa quale metodo deve essere utilizzato in base ai suoi parametri e al tipo di oggetto. Questo è il polimorfismo.

Astrazione

Quando definiamo una classe, stiamo cercando di costruire un modello di qualcosa. Ad esempio, supponiamo di scrivere un videogioco chiamato MyRacer con diverse auto da corsa. Un giocatore può sceglierne uno e successivamente aggiornarlo o acquistarne uno diverso. Quindi... Cos'è un'auto? Un'auto è una cosa piuttosto complicata, ma se stiamo cercando di creare un videogioco di corse (al contrario di un simulatore di guida), allora non abbiamo bisogno di descrivere le migliaia di ingranaggi e guarnizioni che contiene. Abbiamo bisogno del modello, della velocità massima, delle caratteristiche di manovrabilità, del prezzo, del colore... E forse basta. Questo è il modellino di un'auto per il nostro gioco. Più avanti in MyRacer 2, supponiamo di decidere di aggiungere pneumatici che influenzano la manovrabilità su strada. Qui il modello è diverso, perché abbiamo aggiunto più dettagli. Permettere' s definiscono l'astrazione dei dati come il processo di identificazione solo delle caratteristiche importanti (o necessarie) di un oggetto e ignorando eventuali dettagli irrilevanti. Ci sono diversi livelli di astrazione. Ad esempio, se sei un passeggero su un autobus, devi sapere che aspetto ha il tuo autobus e dove sta andando, ma non hai bisogno di sapere come si guida. Se sei un autista di autobus, non hai bisogno di sapere come creare un nuovo autobus, devi solo sapere come guidarlo. Ma se sei un produttore di autobus, devi passare a un livello di astrazione inferiore, perché i dettagli del design dell'autobus sono molto importanti per te. Spero tu capisca quello che intendo. devi sapere che aspetto ha il tuo autobus e dove sta andando, ma non hai bisogno di sapere come guidarlo. Se sei un autista di autobus, non hai bisogno di sapere come creare un nuovo autobus, devi solo sapere come guidarlo. Ma se sei un produttore di autobus, devi passare a un livello di astrazione inferiore, perché i dettagli del design dell'autobus sono molto importanti per te. Spero tu capisca quello che intendo. devi sapere che aspetto ha il tuo autobus e dove sta andando, ma non hai bisogno di sapere come guidarlo. Se sei un autista di autobus, non hai bisogno di sapere come creare un nuovo autobus, devi solo sapere come guidarlo. Ma se sei un produttore di autobus, devi passare a un livello di astrazione inferiore, perché i dettagli del design dell'autobus sono molto importanti per te. Spero tu capisca quello che intendo.

Come funziona in Java:

Costruiamo quattro livelli di astrazione in Java, o meglio in OOP — dal più basso (il più specifico) al più alto (il più astratto).
  1. Il livello più basso di astrazione è un oggetto specifico. È un'entità con un insieme di caratteristiche che appartengono a una classe specifica. Ha valori di campo specifici

  2. Un modello per la creazione di oggetti è una classe. È una descrizione di un insieme di oggetti con proprietà e struttura interna simili.

  3. Una classe astratta è una descrizione astratta delle caratteristiche di un insieme di classi (funge da modello per l'ereditarietà da parte di altre classi). Ha un alto livello di astrazione, quindi è impossibile creare oggetti direttamente da una classe astratta. Solo le classi figlie delle classi astratte possono essere utilizzate per creare oggetti. Una classe astratta può includere metodi con un'implementazione, ma questo non è un requisito.

  4. Un'interfaccia è un costrutto del costrutto del linguaggio di programmazione Java che contiene solo metodi pubblici astratti e campi costanti statici (statico finale). In altre parole, né le classi astratte né le interfacce possono essere utilizzate per generare oggetti.

A proposito, in Java 8 o versioni successive, le interfacce possono avere non solo metodi e costanti astratti, ma anche metodi predefiniti e statici. In Java, un'interfaccia definisce un comportamento, mentre una classe astratta viene utilizzata per creare una gerarchia. Un'interfaccia può essere implementata da più classi.

Esempio di interfaccia in codice Java


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

interface Vulcan {
	int angleOfPointyEars; 
	public void turnOffEmotions(boolean isOn);
	public void telepathy();
}
È possibile implementare più di un'interfaccia

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...");
}

}
Per gli studenti principianti, copre tutti i concetti principali della programmazione orientata agli oggetti in Java. Oltre ai 4 principali principi OOP, Java ha anche associazione, aggregazione e composizione. Puoi chiamarli "principi OOP aggiuntivi". Meritano il loro articolo separato.
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION