oopIniziare con oop


Osservazioni

La programmazione orientata agli oggetti (OOP) è un paradigma di programmazione basato sul concetto di "oggetti", che può contenere dati, sotto forma di campi, spesso noti come attributi; e codice, sotto forma di procedure, spesso conosciute come metodi.

introduzione

OOP - Object Oriented Programming è un paradigma di programmazione ampiamente utilizzato in questi giorni. In OOP, modelliamo i problemi del mondo reale usando Oggetti e i loro comportamenti, al fine di risolverli, a livello di programmazione.

Esistono quattro concetti OOP principali

  1. Eredità
  2. Polimorfismo
  3. Astrazione
  4. incapsulamento

Questi quattro concetti insieme sono usati per sviluppare programmi in OOP.

Esistono varie lingue che supportano la programmazione orientata agli oggetti. Le lingue più popolari sono

  • C ++
  • Giava
  • C #
  • Python (Python non è completamente orientato agli oggetti, ma ha la maggior parte delle sue caratteristiche OOP)

Introduzione OOP

intoduction

La programmazione orientata agli oggetti (principalmente indicata come OOP) è un paradigma di programmazione per risolvere i problemi.
La bellezza di un programma OO (orientato agli oggetti) è che pensiamo al programma come a un gruppo di oggetti che comunicano tra loro, anziché come uno script sequenziale che segue ordini specifici.

Ci sono molti linguaggi di programmazione che supportano OOP, alcuni dei più popolari sono:

  • Giava
  • C ++
  • c #

Python è anche noto per supportare OOP ma manca alcune proprietà.


Terminologia OOP

Il termine più elementare in OOP è una classe .
Una classe è fondamentalmente un oggetto , che ha uno stato e funziona in base al suo stato.

Un altro termine importante è un'istanza .
Pensa a una classe come a un modello usato per creare istanze di se stesso. La classe è un modello e l'istanza (s) è gli oggetti concreti.

Un'istanza creata dalla classe A viene solitamente definita come dal 'tipo A', esattamente come il tipo di 5 è int e il tipo di "abcd" è una stringa .

Un esempio di creazione di un'istanza denominata insance1 di tipo (classe) ClassA :

Giava

ClassA instance1 = new ClassA();
 

C ++

ClassA instance1;
 

o

ClassA *instance1 = new ClassA(); # On the heap
 

Pitone

instance1 = ClassA()
 

Come potete vedere nell'esempio sopra, in tutti i casi è stato menzionato il nome della classe e dopo di esso c'erano parentesi vuote (eccetto per il C ++ dove se sono vuote le parentesi possono essere eliminate). In queste parentesi possiamo passare arguments al costruttore della nostra classe.

Un costruttore è un metodo di una classe che viene chiamato ogni volta che viene creata un'istanza. Può o prendere argomenti o no. Se il programmatore non specifica alcun costruttore per una classe che costruiscono, verrà creato un costruttore vuoto (un costruttore che non fa nulla).
Nella maggior parte delle lingue il costruttore è definito come un metodo senza definire il suo tipo di ritorno e con lo stesso nome della classe (esempio in alcune sezioni).

Un esempio di creazione di un'istanza denominata b1 di tipo (classe) ClassB . Il costruttore di ClassB accetta un argomento di tipo int :

Giava

ClassA instance1 = new ClassA(5);
 

o

int i = 5;
ClassA instance1 = new ClassA(i);
 

C ++

ClassA instance1(5);
 

Pitone

instance1 = ClassA(5)
 

Come puoi vedere, il processo di creazione di un'istanza è molto simile al processo di chiamata di una funzione.


Funzioni vs metodi

Entrambe le funzioni e i metodi sono molto simili, ma in Object Oriented Design (OOD) ognuno di essi ha il proprio significato.
Un metodo è un'operazione eseguita su un'istanza di una classe. Il metodo stesso di solito utilizza lo stato dell'istanza per operare.
Nel frattempo, una funzione appartiene a una classe e non a un'istanza specifica. Ciò significa che non utilizza lo stato della classe o qualsiasi dato memorizzato in un'istanza.

D'ora in poi mostreremo i nostri esempi solo in Java poiché OOP è molto chiaro in questa lingua, ma gli stessi principi funzionano in qualsiasi altro linguaggio OOP.

In Java, una funzione ha la parola static nella sua definizione, in questo modo:

// File's name is ClassA
public static int add(int a, int b) {
    return a + b;
}
 

Ciò significa che puoi chiamarlo da qualsiasi punto dello script.

// From the same file
System.out.println(add(3, 5));

// From another file in the same package (or after imported)
System.out.println(ClassA.add(3, 5));
 

Quando chiamiamo la funzione da un altro file, usiamo il nome della classe (in Java questo è anche il nome del file) a cui appartiene, questo dà l'intuizione che la funzione appartiene alla classe e non a nessuna delle sue istanze.

Al contrario, possiamo definire un metodo in ClassA in questo modo:

// File's name is ClassA
public int subtract(int a, int b){
    return a - b;
}
 

Dopo questa declinazione possiamo chiamare questo metodo in questo modo:

ClassA a = new ClassA();
System.out.println(a.subtract(3, 5));
 

Qui abbiamo avuto bisogno di creare un'istanza di ClassA per chiamare il suo metodo sottrarre. Si noti che NON POSSIAMO fare quanto segue:

System.out.println(ClassA.subtract(3, 5));
 

Questa riga produrrà un errore di compilazione lamentando che abbiamo chiamato questo metodo non statico senza un'istanza.


Usando lo stato di una classe

Supponiamo di voler implementare nuovamente il nostro metodo di sottrazione , ma questa volta vogliamo sempre sottrarre lo stesso numero (per ogni istanza). Possiamo creare la seguente classe:

class ClassB {

    private int sub_amount;

    public ClassB(int sub_amount) {
        this.sub_amount = sub_amount;
    }

    public int subtract(int a) {
        return a - sub_amount;
    }

    public static void main(String[] args) {
        ClassB b = new ClassB(5);
        System.out.println(b.subtract(3)); // Ouput is -2
    }
}
 

Quando eseguiamo questo codice, viene creata una nuova istanza denominata b di classe ClassB e il suo costruttore viene alimentato con il valore 5 .
Il costruttore ora prende il sub_amount dato e lo memorizza come il suo campo privato, chiamato anche sub_amount (questa convenzione è molto conosciuta in Java, per denominare gli argomenti come i campi).
Dopodiché, stampiamo sulla console il risultato della chiamata della sottrazione del metodo su b con il valore di 3 .

Si noti che nell'implementazione della sottrazione non la usiamo this. come nel costruttore.
In Java, this deve essere scritto solo quando esiste un'altra variabile con lo stesso nome definito in quell'ambito. Lo stesso funziona con il self di Python.
Quindi, quando usiamo sub_amount in sottrazione, facciamo riferimento al campo privato che è diverso per ogni classe.

Un altro esempio da sottolineare.
Cambiamo semplicemente la funzione principale nel codice sopra al seguente:

ClassB b1 = new ClassB(1);
ClassB b2 = new ClassB(2);

System.out.println(b1.subtract(10)); // Output is 9
System.out.println(b2.subtract(10)); // Output is 8
 

Come possiamo vedere, b1 e b2 sono indipendenti e ognuno ha il proprio stato .


Interfacce ed eredità

Un'interfaccia è un contratto, esso definisce i metodi di una classe avrà e quindi le sue capacità. Un'interfaccia non ha un'implementazione, ha solo definito ciò che deve essere fatto.
Un esempio in Java potrebbe essere:

interface Printalbe {
    public void print();
}
 

L'interfaccia Printalbe definisce un metodo chiamato print ma non ne fornisce l'implementazione (piuttosto strano per Java). Ogni classe che si dichiara di implementing questa interfaccia deve fornire un'implementazione al metodo draw. Per esempio:

class Person implements Printalbe {

    private String name;

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

    public void print() {
        System.out.println(name);
    }
}
 

Se Person si dichiarasse come implementabile Drawable ma non fornisse un'implementazione per la stampa , ci sarebbe un errore di compilazione e il programma non si compilerebbe.

L'ereditarietà è un termine che indica una classe che estende un'altra classe. Ad esempio, diciamo che ora abbiamo una persona che ha un'età. Un modo per implementare una persona del genere sarebbe copiare la classe Person e scrivere una nuova classe chiamata AgedPerson che ha gli stessi campi e metodi ma ha un'altra proprietà -age.
Questo sarebbe terribile dato che duplichiamo il nostro intero codice solo per aggiungere una semplice funzionalità alla nostra classe.
Possiamo utilizzare l'ereditarietà per ereditare da Persona e quindi ottenere tutte le sue funzionalità, quindi migliorarle con la nostra nuova funzione, in questo modo:

class AgedPerson extends Person {

    private int age;

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

    public void print() {
        System.out.println("Name: " + name + ", age:" + age);
    }
}
 

Ci sono alcune novità in corso:

  • Abbiamo usato la parola salvata si extends per indicare che stiamo ereditando da Person (e anche la sua implementazione in Printable , quindi non abbiamo bisogno di dichiarare di nuovo implementing Printable ).
  • Abbiamo usato la parola di salvataggio super per chiamare il costruttore di Person .
  • Abbiamo sostituito il metodo di stampa di Person con uno nuovo.

Questo sta diventando piuttosto tecnico Java quindi non approfondirò questo argomento. Ma menzionerò che ci sono molti casi estremi che dovrebbero essere appresi sull'ereditarietà e le interfacce prima di iniziare ad usarli. Ad esempio quali metodi e funzioni sono ereditati? Cosa succede ai campi privati ​​/ pubblici / protetti quando si eredita da una classe? e così via.

Classe astratta

Una classe astratta è un termine piuttosto avanzato in OOP che descrive una combinazione di entrambe le interfacce e l'ereditarietà. Ti permette di scrivere una classe che ha sia metodi implementati che non implementati. In Java ciò avviene usando la parola chiave abstract e non lo spiegherò più di un rapido esempio:

abstract class AbstractIntStack {

    abstract public void push(int element);

    abstract public void pop();

    abstract public int top();

    final public void replaceTop(int element) {
        pop();
        push(element);
    }
}
 

Nota: la parola chiave final afferma che non è possibile sovrascrivere questo metodo quando si eredita da questa classe. Se una classe è dichiarata definitiva, nessuna classe può ereditarla affatto.