Εισαγωγή στον αντικειμενοστραφή προγραμματισμό
Ο όρος “OOP” αναφέρεται στον αντικειμενοστραφή προγραμματισμό, ο οποίος είναι ένας τρόπος οργάνωσης και δόμησης του κώδικα. Ο OOP μας επιτρέπει να βλέπουμε ένα πρόγραμμα ως ένα σύνολο αντικειμένων που επικοινωνούν μεταξύ τους, αντί για μια ακολουθία εντολών και συναρτήσεων.
Στον OOP, ένα “αντικείμενο” είναι μια μονάδα που περιέχει δεδομένα και συναρτήσεις που λειτουργούν με αυτά τα δεδομένα. Τα αντικείμενα δημιουργούνται σύμφωνα με “κλάσεις”, τις οποίες μπορούμε να κατανοήσουμε ως σχέδια ή πρότυπα για αντικείμενα. Όταν έχουμε μια κλάση, μπορούμε να δημιουργήσουμε ένα “στιγμιότυπο” της, το οποίο είναι ένα συγκεκριμένο αντικείμενο που δημιουργήθηκε σύμφωνα με αυτή την κλάση.
Ας δείξουμε πώς μπορούμε να δημιουργήσουμε μια απλή κλάση στην PHP. Κατά τον ορισμό μιας κλάσης, χρησιμοποιούμε τη λέξη-κλειδί “class”, ακολουθούμενη από το όνομα της κλάσης και στη συνέχεια αγκύλες που περικλείουν τις συναρτήσεις (ονομάζονται “μέθοδοι”) και τις μεταβλητές της κλάσης (ονομάζονται “ιδιότητες” ή “property” στα αγγλικά):
class Aftokinito
{
function korna()
{
echo 'Μπιπ μπιπ!';
}
}
Σε αυτό το παράδειγμα, δημιουργήσαμε μια κλάση με το όνομα
Aftokinito
με μία συνάρτηση (ή “μέθοδο”) που ονομάζεται korna
.
Κάθε κλάση πρέπει να επιλύει μόνο μία κύρια εργασία. Εάν μια κλάση κάνει πάρα πολλά πράγματα, μπορεί να είναι σκόπιμο να την χωρίσετε σε μικρότερες, εξειδικευμένες κλάσεις.
Οι κλάσεις συνήθως αποθηκεύονται σε ξεχωριστά αρχεία, ώστε ο κώδικας
να είναι οργανωμένος και εύκολος στην πλοήγηση. Το όνομα του αρχείου
πρέπει να αντιστοιχεί στο όνομα της κλάσης, οπότε για την κλάση
Aftokinito
, το όνομα του αρχείου θα ήταν Aftokinito.php
.
Κατά την ονομασία των κλάσεων, είναι καλό να ακολουθείτε τη σύμβαση “PascalCase”, που σημαίνει ότι κάθε λέξη στο όνομα ξεκινά με κεφαλαίο γράμμα και δεν υπάρχουν κάτω παύλες ή άλλοι διαχωριστές μεταξύ τους. Οι μέθοδοι και οι ιδιότητες χρησιμοποιούν τη σύμβαση “camelCase”, που σημαίνει ότι ξεκινούν με μικρό γράμμα.
Ορισμένες μέθοδοι στην PHP έχουν ειδικούς ρόλους και επισημαίνονται
με το πρόθεμα __
(δύο κάτω παύλες). Μία από τις πιο σημαντικές
ειδικές μεθόδους είναι ο “κατασκευαστής”, ο οποίος επισημαίνεται ως
__construct
. Ο κατασκευαστής είναι μια μέθοδος που καλείται αυτόματα
όταν δημιουργείτε ένα νέο στιγμιότυπο της κλάσης.
Ο κατασκευαστής χρησιμοποιείται συχνά για να ορίσει την αρχική κατάσταση του αντικειμένου. Για παράδειγμα, όταν δημιουργείτε ένα αντικείμενο που αντιπροσωπεύει ένα άτομο, μπορείτε να χρησιμοποιήσετε τον κατασκευαστή για να ορίσετε την ηλικία, το όνομα ή άλλες ιδιότητές του.
Ας δείξουμε πώς να χρησιμοποιήσετε έναν κατασκευαστή στην PHP:
class Atomο
{
private $ilikia;
function __construct($ilikia)
{
$this->ilikia = $ilikia;
}
function posoChrononEisai()
{
return $this->ilikia;
}
}
$atomo = new Atomο(25);
echo $atomo->posoChrononEisai(); // Εκτυπώνει: 25
Σε αυτό το παράδειγμα, η κλάση Atomο
έχει μια ιδιότητα
(μεταβλητή) $ilikia
και έναν κατασκευαστή που ορίζει αυτή την
ιδιότητα. Η μέθοδος posoChrononEisai()
επιτρέπει στη συνέχεια την
πρόσβαση στην ηλικία του ατόμου.
Η ψευδομεταβλητή $this
χρησιμοποιείται μέσα στην κλάση για
πρόσβαση στις ιδιότητες και τις μεθόδους του αντικειμένου.
Η λέξη-κλειδί new
χρησιμοποιείται για τη δημιουργία ενός νέου
στιγμιότυπου της κλάσης. Στο παραπάνω παράδειγμα, δημιουργήσαμε ένα
νέο άτομο με ηλικία 25.
Μπορείτε επίσης να ορίσετε προεπιλεγμένες τιμές για τις παραμέτρους του κατασκευαστή, εάν δεν καθορίζονται κατά τη δημιουργία του αντικειμένου. Για παράδειγμα:
class Atomο
{
private $ilikia;
function __construct($ilikia = 20)
{
$this->ilikia = $ilikia;
}
function posoChrononEisai()
{
return $this->ilikia;
}
}
$atomo = new Atomο; // αν δεν περνάμε κανένα όρισμα, οι παρενθέσεις μπορούν να παραλειφθούν
echo $atomo->posoChrononEisai(); // Εκτυπώνει: 20
Σε αυτό το παράδειγμα, εάν δεν καθορίσετε την ηλικία κατά τη
δημιουργία του αντικειμένου Atomο
, θα χρησιμοποιηθεί η
προεπιλεγμένη τιμή 20.
Είναι ευχάριστο το γεγονός ότι ο ορισμός της ιδιότητας με την αρχικοποίησή της μέσω του κατασκευαστή μπορεί να συντομευτεί και να απλοποιηθεί ως εξής:
class Atomο
{
function __construct(
private $ilikia = 20,
) {
}
}
Για λόγους πληρότητας, εκτός από τους κατασκευαστές, τα αντικείμενα
μπορούν να έχουν και καταστροφείς (μέθοδος __destruct
), οι οποίοι
καλούνται πριν το αντικείμενο απελευθερωθεί από τη μνήμη.
Ονοματοχώροι
Οι ονοματοχώροι (ή “namespaces” στα αγγλικά) μας επιτρέπουν να οργανώνουμε και να ομαδοποιούμε σχετικές κλάσεις, συναρτήσεις και σταθερές, αποφεύγοντας ταυτόχρονα τις συγκρούσεις ονομάτων. Μπορείτε να τους φανταστείτε σαν φακέλους στον υπολογιστή σας, όπου κάθε φάκελος περιέχει αρχεία που ανήκουν σε ένα συγκεκριμένο έργο ή θέμα.
Οι ονοματοχώροι είναι ιδιαίτερα χρήσιμοι σε μεγαλύτερα έργα ή όταν χρησιμοποιείτε βιβλιοθήκες τρίτων, όπου θα μπορούσαν να προκύψουν συγκρούσεις στα ονόματα των κλάσεων.
Φανταστείτε ότι έχετε μια κλάση με το όνομα Aftokinito
στο έργο σας
και θέλετε να την τοποθετήσετε σε έναν ονοματοχώρο που ονομάζεται
Metafores
. Θα το κάνετε ως εξής:
namespace Metafores;
class Aftokinito
{
function korna()
{
echo 'Μπιπ μπιπ!';
}
}
Αν θέλετε να χρησιμοποιήσετε την κλάση Aftokinito
σε ένα άλλο
αρχείο, πρέπει να καθορίσετε από ποιον ονοματοχώρο προέρχεται
η κλάση:
$auto = new Metafores\Aftokinito;
Για απλοποίηση, μπορείτε στην αρχή του αρχείου να δηλώσετε ποια κλάση από τον δεδομένο ονοματοχώρο θέλετε να χρησιμοποιήσετε, επιτρέποντας τη δημιουργία στιγμιότυπων χωρίς να χρειάζεται να αναφέρετε ολόκληρη τη διαδρομή:
use Metafores\Aftokinito;
$auto = new Aftokinito;
Κληρονομικότητα
Η κληρονομικότητα είναι ένα εργαλείο του αντικειμενοστραφούς προγραμματισμού που επιτρέπει τη δημιουργία νέων κλάσεων βάσει ήδη υπαρχουσών κλάσεων, την ανάληψη των ιδιοτήτων και των μεθόδων τους και την επέκταση ή τον επαναπροσδιορισμό τους ανάλογα με τις ανάγκες. Η κληρονομικότητα επιτρέπει την εξασφάλιση της επαναχρησιμοποίησης του κώδικα και την ιεραρχία των κλάσεων.
Με απλά λόγια, αν έχουμε μια κλάση και θέλουμε να δημιουργήσουμε μια άλλη, παράγωγη από αυτήν, αλλά με μερικές αλλαγές, μπορούμε να “κληρονομήσουμε” τη νέα κλάση από την αρχική κλάση.
Στην PHP, η κληρονομικότητα υλοποιείται με τη χρήση της λέξης-κλειδιού
extends
.
Η κλάση μας Atomο
αποθηκεύει πληροφορίες για την ηλικία.
Μπορούμε να έχουμε μια άλλη κλάση Foititis
, η οποία επεκτείνει την
Atomο
και προσθέτει πληροφορίες για τον τομέα σπουδών.
Ας δούμε ένα παράδειγμα:
class Atomο
{
private $ilikia;
function __construct($ilikia)
{
$this->ilikia = $ilikia;
}
function emfanisePlirofories()
{
echo "Ηλικία: {$this->ilikia} έτη\n";
}
}
class Foititis extends Atomο
{
private $tomeas;
function __construct($ilikia, $tomeas)
{
parent::__construct($ilikia);
$this->tomeas = $tomeas;
}
function emfanisePlirofories()
{
parent::emfanisePlirofories();
echo "Τομέας σπουδών: {$this->tomeas} \n";
}
}
$foititis = new Foititis(20, 'Πληροφορική');
$foititis->emfanisePlirofories();
Πώς λειτουργεί αυτός ο κώδικας;
- Χρησιμοποιήσαμε τη λέξη-κλειδί
extends
για να επεκτείνουμε την κλάσηAtomο
, πράγμα που σημαίνει ότι η κλάσηFoititis
κληρονομεί όλες τις μεθόδους και τις ιδιότητες από τηνAtomο
. - Η λέξη-κλειδί
parent::
μας επιτρέπει να καλούμε μεθόδους από την γονική κλάση. Σε αυτή την περίπτωση, καλέσαμε τον κατασκευαστή από την κλάσηAtomο
πριν προσθέσουμε τη δική μας λειτουργικότητα στην κλάσηFoititis
. Και ομοίως, τη μέθοδοemfanisePlirofories()
του προγόνου πριν εμφανίσουμε τις πληροφορίες για τον φοιτητή.
Η κληρονομικότητα προορίζεται για καταστάσεις όπου υπάρχει μια
σχέση “είναι” μεταξύ των κλάσεων. Για παράδειγμα, ο Foititis
είναι
Atomο
. Η γάτα είναι ζώο. Μας δίνει τη δυνατότητα σε περιπτώσεις
όπου ο κώδικας αναμένει ένα αντικείμενο (π.χ. “Atomο”), να
χρησιμοποιήσουμε αντ' αυτού ένα κληρονομημένο αντικείμενο (π.χ.
“Foititis”).
Είναι σημαντικό να συνειδητοποιήσουμε ότι ο κύριος σκοπός της κληρονομικότητας δεν είναι η αποφυγή της διπλοτυπίας του κώδικα. Αντίθετα, η λανθασμένη χρήση της κληρονομικότητας μπορεί να οδηγήσει σε πολύπλοκο και δύσκολα συντηρήσιμο κώδικα. Εάν η σχέση “είναι” μεταξύ των κλάσεων δεν υπάρχει, θα πρέπει να εξετάσουμε τη σύνθεση αντί της κληρονομικότητας.
Σημειώστε ότι οι μέθοδοι emfanisePlirofories()
στις κλάσεις Atomο
και Foititis
εμφανίζουν ελαφρώς διαφορετικές πληροφορίες. Και
μπορούμε να προσθέσουμε και άλλες κλάσεις (για παράδειγμα
Ypallilos
), οι οποίες θα παρέχουν άλλες υλοποιήσεις αυτής της
μεθόδου. Η ικανότητα των αντικειμένων διαφορετικών κλάσεων να
αντιδρούν στην ίδια μέθοδο με διαφορετικούς τρόπους ονομάζεται
πολυμορφισμός:
$atoma = [
new Atomο(30),
new Foititis(20, 'Πληροφορική'),
new Ypallilos(45, 'Διευθυντής'),
];
foreach ($atoma as $atomo) {
$atomo->emfanisePlirofories();
}
Σύνθεση
Η σύνθεση είναι μια τεχνική όπου, αντί να κληρονομούμε τις ιδιότητες και τις μεθόδους μιας άλλης κλάσης, απλώς χρησιμοποιούμε ένα στιγμιότυπό της στην κλάση μας. Αυτό μας επιτρέπει να συνδυάζουμε λειτουργικότητες και ιδιότητες πολλαπλών κλάσεων χωρίς την ανάγκη δημιουργίας πολύπλοκων κληρονομικών δομών.
Ας δούμε ένα παράδειγμα. Έχουμε μια κλάση Kinitiras
και μια κλάση
Aftokinito
. Αντί να λέμε “Το αυτοκίνητο είναι κινητήρας”, λέμε “Το
αυτοκίνητο έχει κινητήρα”, που είναι μια τυπική σχέση σύνθεσης.
class Kinitiras
{
function ekkinise()
{
echo 'Ο κινητήρας λειτουργεί.';
}
}
class Aftokinito
{
private $motor;
function __construct()
{
$this->motor = new Kinitiras;
}
function start()
{
$this->motor->ekkinise();
echo 'Το αυτοκίνητο είναι έτοιμο για οδήγηση!';
}
}
$auto = new Aftokinito;
$auto->start();
Εδώ, η κλάση Aftokinito
δεν έχει όλες τις ιδιότητες και τις μεθόδους
της κλάσης Kinitiras
, αλλά έχει πρόσβαση σε αυτήν μέσω της ιδιότητας
$motor
.
Το πλεονέκτημα της σύνθεσης είναι η μεγαλύτερη ευελιξία στο σχεδιασμό και η καλύτερη δυνατότητα τροποποιήσεων στο μέλλον.
Ορατότητα
Στην PHP, μπορείτε να ορίσετε την “ορατότητα” για τις ιδιότητες, τις μεθόδους και τις σταθερές μιας κλάσης. Η ορατότητα καθορίζει από πού μπορείτε να έχετε πρόσβαση σε αυτά τα στοιχεία.
- Public: Εάν ένα στοιχείο επισημαίνεται ως
public
, σημαίνει ότι μπορείτε να έχετε πρόσβαση σε αυτό από οπουδήποτε, ακόμα και εκτός της κλάσης. - Protected: Ένα στοιχείο με την επισήμανση
protected
είναι προσβάσιμο μόνο εντός της συγκεκριμένης κλάσης και όλων των απογόνων της (κλάσεων που κληρονομούν από αυτή την κλάση). - Private: Εάν ένα στοιχείο είναι
private
, μπορείτε να έχετε πρόσβαση σε αυτό μόνο από το εσωτερικό της κλάσης στην οποία ορίστηκε.
Εάν δεν καθορίσετε την ορατότητα, η PHP την ορίζει αυτόματα σε
public
.
Ας δούμε ένα δείγμα κώδικα:
class ParadeigmaOratotitas
{
public $dimosiaIdiotita = 'Δημόσια';
protected $prostatevmeniIdiotita = 'Προστατευμένη';
private $idiotikiIdiotita = 'Ιδιωτική';
public function emfaniseIdiotites()
{
echo $this->dimosiaIdiotita; // Λειτουργεί
echo $this->prostatevmeniIdiotita; // Λειτουργεί
echo $this->idiotikiIdiotita; // Λειτουργεί
}
}
$objekt = new ParadeigmaOratotitas;
$objekt->emfaniseIdiotites();
echo $objekt->dimosiaIdiotita; // Λειτουργεί
// echo $objekt->prostatevmeniIdiotita; // Προκαλεί σφάλμα
// echo $objekt->idiotikiIdiotita; // Προκαλεί σφάλμα
Συνεχίζουμε με την κληρονομικότητα της κλάσης:
class ApogonosKlaseis extends ParadeigmaOratotitas
{
public function emfaniseIdiotites()
{
echo $this->dimosiaIdiotita; // Λειτουργεί
echo $this->prostatevmeniIdiotita; // Λειτουργεί
// echo $this->idiotikiIdiotita; // Προκαλεί σφάλμα
}
}
Σε αυτή την περίπτωση, η μέθοδος emfaniseIdiotites()
στην κλάση
ApogonosKlaseis
μπορεί να έχει πρόσβαση στις δημόσιες και
προστατευμένες ιδιότητες, αλλά δεν μπορεί να έχει πρόσβαση στις
ιδιωτικές ιδιότητες της γονικής κλάσης.
Τα δεδομένα και οι μέθοδοι πρέπει να είναι όσο το δυνατόν πιο κρυμμένα και προσβάσιμα μόνο μέσω μιας καθορισμένης διεπαφής. Αυτό σας επιτρέπει να αλλάξετε την εσωτερική υλοποίηση της κλάσης χωρίς να επηρεάσετε τον υπόλοιπο κώδικα.
Λέξη-κλειδί final
Στην PHP, μπορούμε να χρησιμοποιήσουμε τη λέξη-κλειδί final
, εάν
θέλουμε να αποτρέψουμε την κληρονομικότητα ή την παράκαμψη μιας
κλάσης, μεθόδου ή σταθεράς. Όταν επισημαίνουμε μια κλάση ως final
,
δεν μπορεί να επεκταθεί. Όταν επισημαίνουμε μια μέθοδο ως final
,
δεν μπορεί να παρακαμφθεί σε μια κλάση απογόνου.
Η γνώση ότι μια συγκεκριμένη κλάση ή μέθοδος δεν θα τροποποιηθεί περαιτέρω μας επιτρέπει να κάνουμε αλλαγές πιο εύκολα, χωρίς να χρειάζεται να ανησυχούμε για πιθανές συγκρούσεις. Για παράδειγμα, μπορούμε να προσθέσουμε μια νέα μέθοδο χωρίς να ανησυχούμε ότι κάποιος από τους απογόνους της έχει ήδη μια μέθοδο με το ίδιο όνομα και θα προκληθεί σύγκρουση. Ή μπορούμε να αλλάξουμε τις παραμέτρους μιας μεθόδου, καθώς και πάλι δεν υπάρχει κίνδυνος να προκαλέσουμε ασυμφωνία με την παρακαμφθείσα μέθοδο στον απόγονο.
final class TelikiKlasi
{
}
// Ο παρακάτω κώδικας θα προκαλέσει σφάλμα, επειδή δεν μπορούμε να κληρονομήσουμε από μια τελική κλάση.
class ApogonosTelikisKlasis extends TelikiKlasi
{
}
Σε αυτό το παράδειγμα, η προσπάθεια κληρονομικότητας από την τελική
κλάση TelikiKlasi
θα προκαλέσει σφάλμα.
Στατικές ιδιότητες και μέθοδοι
Όταν στην PHP μιλάμε για “στατικά” στοιχεία μιας κλάσης, εννοούμε μεθόδους και ιδιότητες που ανήκουν στην ίδια την κλάση, και όχι σε ένα συγκεκριμένο στιγμιότυπο αυτής της κλάσης. Αυτό σημαίνει ότι δεν χρειάζεται να δημιουργήσετε ένα στιγμιότυπο της κλάσης για να έχετε πρόσβαση σε αυτά. Αντ' αυτού, τα καλείτε ή έχετε πρόσβαση σε αυτά απευθείας μέσω του ονόματος της κλάσης.
Έχετε υπόψη ότι, καθώς τα στατικά στοιχεία ανήκουν στην κλάση και όχι
στα στιγμιότυπά της, δεν μπορείτε να χρησιμοποιήσετε την
ψευδομεταβλητή $this
μέσα σε στατικές μεθόδους.
Η χρήση στατικών ιδιοτήτων οδηγεί σε κώδικα που προκαλεί σύγχυση και είναι γεμάτος παγίδες, γι' αυτό δεν πρέπει ποτέ να τις χρησιμοποιείτε και ούτε θα δείξουμε εδώ παράδειγμα χρήσης. Αντίθετα, οι στατικές μέθοδοι είναι χρήσιμες. Παράδειγμα χρήσης:
class Ypologistis
{
public static function prosthesi($a, $b)
{
return $a + $b;
}
public static function afairesi($a, $b)
{
return $a - $b;
}
}
// Χρήση στατικής μεθόδου χωρίς δημιουργία στιγμιότυπου της κλάσης
echo Ypologistis::prosthesi(5, 3); // Αποτέλεσμα: 8
echo Ypologistis::afairesi(5, 3); // Αποτέλεσμα: 2
Σε αυτό το παράδειγμα, δημιουργήσαμε μια κλάση Ypologistis
με δύο
στατικές μεθόδους. Μπορούμε να καλέσουμε αυτές τις μεθόδους απευθείας
χωρίς να δημιουργήσουμε ένα στιγμιότυπο της κλάσης χρησιμοποιώντας
τον τελεστή ::
. Οι στατικές μέθοδοι είναι ιδιαίτερα χρήσιμες για
λειτουργίες που δεν εξαρτώνται από την κατάσταση ενός συγκεκριμένου
στιγμιότυπου της κλάσης.
Σταθερές κλάσης
Μέσα στις κλάσεις, έχουμε τη δυνατότητα να ορίσουμε σταθερές. Οι σταθερές είναι τιμές που δεν αλλάζουν ποτέ κατά τη διάρκεια της εκτέλεσης του προγράμματος. Σε αντίθεση με τις μεταβλητές, η τιμή μιας σταθεράς παραμένει πάντα η ίδια.
class Aftokinito
{
public const ArithmosTrochon = 4;
public function emfaniseArithmoTrochon(): int
{
echo self::ArithmosTrochon;
}
}
echo Aftokinito::ArithmosTrochon; // Έξοδος: 4
Σε αυτό το παράδειγμα, έχουμε την κλάση Aftokinito
με τη σταθερά
ArithmosTrochon
. Όταν θέλουμε να έχουμε πρόσβαση στη σταθερά μέσα στην
κλάση, μπορούμε να χρησιμοποιήσουμε τη λέξη-κλειδί self
αντί για
το όνομα της κλάσης.
Διεπαφές αντικειμένων
Οι διεπαφές αντικειμένων λειτουργούν ως “συμβόλαια” για τις κλάσεις. Εάν μια κλάση πρόκειται να υλοποιήσει μια διεπαφή αντικειμένου, πρέπει να περιέχει όλες τις μεθόδους που ορίζει αυτή η διεπαφή. Είναι ένας εξαιρετικός τρόπος για να διασφαλιστεί ότι ορισμένες κλάσεις τηρούν το ίδιο “συμβόλαιο” ή δομή.
Στην PHP, μια διεπαφή ορίζεται με τη λέξη-κλειδί interface
. Όλες οι
μέθοδοι που ορίζονται σε μια διεπαφή είναι δημόσιες (public
). Όταν
μια κλάση υλοποιεί μια διεπαφή, χρησιμοποιεί τη λέξη-κλειδί
implements
.
interface Zoo
{
function kaneIxο();
}
class Gata implements Zoo
{
public function kaneIxο()
{
echo 'Νιάου';
}
}
$kocka = new Gata;
$kocka->kaneIxο();
Εάν μια κλάση υλοποιεί μια διεπαφή, αλλά δεν ορίζονται σε αυτήν όλες οι αναμενόμενες μέθοδοι, η PHP θα προκαλέσει σφάλμα.
Μια κλάση μπορεί να υλοποιεί πολλαπλές διεπαφές ταυτόχρονα, πράγμα που διαφέρει από την κληρονομικότητα, όπου μια κλάση μπορεί να κληρονομήσει μόνο από μία κλάση:
interface Fylakas
{
function fylaxeSpiti();
}
class Skylos implements Zoo, Fylakas
{
public function kaneIxο()
{
echo 'Γαβ';
}
public function fylaxeSpiti()
{
echo 'Ο σκύλος φυλάει προσεκτικά το σπίτι';
}
}
Αφηρημένες κλάσεις
Οι αφηρημένες κλάσεις χρησιμεύουν ως βασικά πρότυπα για άλλες κλάσεις, αλλά δεν μπορείτε να δημιουργήσετε στιγμιότυπά τους απευθείας. Περιέχουν έναν συνδυασμό πλήρων μεθόδων και αφηρημένων μεθόδων, οι οποίες δεν έχουν ορισμένο περιεχόμενο. Οι κλάσεις που κληρονομούν από αφηρημένες κλάσεις πρέπει να παρέχουν ορισμούς για όλες τις αφηρημένες μεθόδους του προγόνου.
Για να ορίσουμε μια αφηρημένη κλάση, χρησιμοποιούμε τη λέξη-κλειδί
abstract
.
abstract class AfirimeniKlasi
{
public function synithismeniMethodos()
{
echo 'Αυτή είναι μια συνηθισμένη μέθοδος';
}
abstract public function afirimeniMethodos();
}
class Apogonos extends AfirimeniKlasi
{
public function afirimeniMethodos()
{
echo 'Αυτή είναι η υλοποίηση της αφηρημένης μεθόδου';
}
}
$instance = new Apogonos;
$instance->synithismeniMethodos();
$instance->afirimeniMethodos();
Σε αυτό το παράδειγμα, έχουμε μια αφηρημένη κλάση με μία συνηθισμένη
και μία αφηρημένη μέθοδο. Στη συνέχεια, έχουμε την κλάση Apogonos
, η
οποία κληρονομεί από την AfirimeniKlasi
και παρέχει την υλοποίηση για
την αφηρημένη μέθοδο.
Πώς διαφέρουν στην πραγματικότητα οι διεπαφές και οι αφηρημένες κλάσεις; Οι αφηρημένες κλάσεις μπορούν να περιέχουν τόσο αφηρημένες όσο και συγκεκριμένες μεθόδους, ενώ οι διεπαφές ορίζουν μόνο ποιες μεθόδους πρέπει να υλοποιήσει μια κλάση, αλλά δεν παρέχουν καμία υλοποίηση. Μια κλάση μπορεί να κληρονομήσει μόνο από μία αφηρημένη κλάση, αλλά μπορεί να υλοποιήσει οποιονδήποτε αριθμό διεπαφών.
Έλεγχος τύπων
Στον προγραμματισμό, είναι πολύ σημαντικό να είμαστε σίγουροι ότι τα δεδομένα με τα οποία εργαζόμαστε είναι του σωστού τύπου. Στην PHP, έχουμε εργαλεία που μας το εξασφαλίζουν αυτό. Η επαλήθευση ότι τα δεδομένα έχουν τον σωστό τύπο ονομάζεται “έλεγχος τύπων”.
Οι τύποι που μπορούμε να συναντήσουμε στην PHP:
- Βασικοί τύποι: Περιλαμβάνουν
int
(ακέραιοι αριθμοί),float
(δεκαδικοί αριθμοί),bool
(τιμές αλήθειας),string
(συμβολοσειρές),array
(πίνακες) καιnull
. - Κλάσεις: Εάν θέλουμε η τιμή να είναι στιγμιότυπο μιας συγκεκριμένης κλάσης.
- Διεπαφές: Ορίζει ένα σύνολο μεθόδων που πρέπει να υλοποιήσει μια κλάση. Μια τιμή που ικανοποιεί τη διεπαφή πρέπει να έχει αυτές τις μεθόδους.
- Μικτοί τύποι: Μπορούμε να καθορίσουμε ότι μια μεταβλητή μπορεί να έχει πολλούς επιτρεπόμενους τύπους.
- Void: Αυτός ο ειδικός τύπος υποδηλώνει ότι μια συνάρτηση ή μέθοδος δεν επιστρέφει καμία τιμή.
Ας δείξουμε πώς να τροποποιήσουμε τον κώδικα ώστε να περιλαμβάνει τύπους:
class Atomο
{
private int $ilikia;
public function __construct(int $ilikia)
{
$this->ilikia = $ilikia;
}
public function emfaniseIlikia(): void
{
echo "Αυτό το άτομο είναι {$this->ilikia} ετών.";
}
}
/**
* Συνάρτηση που δέχεται ένα αντικείμενο της κλάσης Atomο και εμφανίζει την ηλικία του ατόμου.
*/
function emfaniseIlikiaAtomou(Atomο $atomo): void
{
$atomo->emfaniseIlikia();
}
Με αυτόν τον τρόπο, διασφαλίσαμε ότι ο κώδικάς μας αναμένει και λειτουργεί με δεδομένα του σωστού τύπου, πράγμα που μας βοηθά να προλαμβάνουμε πιθανά σφάλματα.
Ορισμένοι τύποι δεν μπορούν να γραφτούν απευθείας στην PHP. Σε αυτή την
περίπτωση, αναφέρονται στο σχόλιο phpDoc, το οποίο είναι ένα τυπικό
μορφότυπο για την τεκμηρίωση του κώδικα PHP που ξεκινά με /**
και
τελειώνει με */
. Επιτρέπει την προσθήκη περιγραφών κλάσεων,
μεθόδων κ.λπ. Και επίσης την αναφορά σύνθετων τύπων με τη χρήση των
λεγόμενων σχολιασμών @var
, @param
και @return
. Αυτοί οι
τύποι χρησιμοποιούνται στη συνέχεια από εργαλεία για τη στατική
ανάλυση του κώδικα, αλλά η ίδια η PHP δεν τους ελέγχει.
class Lista
{
/** @var array<Atomο> η σημειογραφία δηλώνει ότι πρόκειται για έναν πίνακα αντικειμένων Atomο */
private array $atoma = [];
public function prosthikiAtomou(Atomο $atomo): void
{
$this->atoma[] = $atomo;
}
}
Σύγκριση και ταυτότητα
Στην PHP, μπορείτε να συγκρίνετε αντικείμενα με δύο τρόπους:
- Σύγκριση τιμών
==
: Ελέγχει εάν τα αντικείμενα είναι της ίδιας κλάσης και έχουν τις ίδιες τιμές στις ιδιότητές τους. - Ταυτότητα
===
: Ελέγχει εάν πρόκειται για το ίδιο στιγμιότυπο του αντικειμένου.
class Aftokinito
{
public string $marka;
public function __construct(string $marka)
{
$this->marka = $marka;
}
}
$auto1 = new Aftokinito('Skoda');
$auto2 = new Aftokinito('Skoda');
$auto3 = $auto1;
var_dump($auto1 == $auto2); // true, επειδή έχουν την ίδια τιμή
var_dump($auto1 === $auto2); // false, επειδή δεν είναι το ίδιο στιγμιότυπο
var_dump($auto1 === $auto3); // true, επειδή το $auto3 είναι το ίδιο στιγμιότυπο με το $auto1
Τελεστής instanceof
Ο τελεστής instanceof
επιτρέπει να διαπιστώσετε εάν ένα δεδομένο
αντικείμενο είναι στιγμιότυπο μιας συγκεκριμένης κλάσης, απόγονος
αυτής της κλάσης, ή εάν υλοποιεί μια συγκεκριμένη διεπαφή.
Ας φανταστούμε ότι έχουμε μια κλάση Atomο
και μια άλλη κλάση
Foititis
, η οποία είναι απόγονος της κλάσης Atomο
:
class Atomο
{
private int $ilikia;
public function __construct(int $ilikia)
{
$this->ilikia = $ilikia;
}
}
class Foititis extends Atomο
{
private string $tomeas;
public function __construct(int $ilikia, string $tomeas)
{
parent::__construct($ilikia);
$this->tomeas = $tomeas;
}
}
$student = new Foititis(20, 'Πληροφορική');
// Έλεγχος εάν το $student είναι στιγμιότυπο της κλάσης Student
var_dump($student instanceof Foititis); // Έξοδος: bool(true)
// Έλεγχος εάν το $student είναι στιγμιότυπο της κλάσης Atomο (επειδή το Student είναι απόγονος του Atomο)
var_dump($student instanceof Atomο); // Έξοδος: bool(true)
Από τις εξόδους είναι φανερό ότι το αντικείμενο $student
θεωρείται ταυτόχρονα στιγμιότυπο και των δύο κλάσεων – Foititis
και Atomο
.
Fluent Interfaces
Η “Ρέουσα διεπαφή” (αγγλικά “Fluent Interface”) είναι μια τεχνική στον OOP που επιτρέπει την αλυσιδωτή σύνδεση μεθόδων σε μία κλήση. Αυτό συχνά απλοποιεί και καθιστά τον κώδικα πιο ευανάγνωστο.
Το βασικό στοιχείο της ρέουσας διεπαφής είναι ότι κάθε μέθοδος στην
αλυσίδα επιστρέφει μια αναφορά στο τρέχον αντικείμενο. Αυτό
επιτυγχάνεται χρησιμοποιώντας return $this;
στο τέλος της μεθόδου.
Αυτό το στυλ προγραμματισμού συνδέεται συχνά με μεθόδους που
ονομάζονται “setters”, οι οποίες ορίζουν τις τιμές των ιδιοτήτων του
αντικειμένου.
Ας δείξουμε πώς μπορεί να μοιάζει μια ρέουσα διεπαφή σε ένα παράδειγμα αποστολής email:
public function apostoliMinimatos()
{
$email = new Email;
$email->setFrom('sender@example.com')
->setRecipient('admin@example.com')
->setMessage('Hello, this is a message.')
->send();
}
Σε αυτό το παράδειγμα, οι μέθοδοι setFrom()
, setRecipient()
και
setMessage()
χρησιμεύουν για τον ορισμό των αντίστοιχων τιμών
(αποστολέας, παραλήπτης, περιεχόμενο μηνύματος). Αφού οριστεί κάθε μία
από αυτές τις τιμές, οι μέθοδοι μας επιστρέφουν το τρέχον αντικείμενο
($email
), επιτρέποντάς μας να συνδέσουμε την επόμενη μέθοδο μετά
από αυτήν. Τέλος, καλούμε τη μέθοδο send()
, η οποία στέλνει
πραγματικά το email.
Χάρη στις ρέουσες διεπαφές, μπορούμε να γράψουμε κώδικα που είναι διαισθητικός και εύκολα αναγνώσιμος.
Αντιγραφή με clone
Στην PHP, μπορούμε να δημιουργήσουμε ένα αντίγραφο ενός αντικειμένου
χρησιμοποιώντας τον τελεστή clone
. Με αυτόν τον τρόπο, λαμβάνουμε
ένα νέο στιγμιότυπο με πανομοιότυπο περιεχόμενο.
Εάν χρειάζεται να τροποποιήσουμε ορισμένες ιδιότητες ενός
αντικειμένου κατά την αντιγραφή του, μπορούμε να ορίσουμε στην κλάση
μια ειδική μέθοδο __clone()
. Αυτή η μέθοδος καλείται αυτόματα όταν
το αντικείμενο κλωνοποιείται.
class Provato
{
public string $onoma;
public function __construct(string $onoma)
{
$this->onoma = $onoma;
}
public function __clone()
{
$this->onoma = 'Κλώνος ' . $this->onoma;
}
}
$original = new Provato('Dolly');
echo $original->onoma . "\n"; // Εκτυπώνει: Dolly
$klon = clone $original;
echo $klon->onoma . "\n"; // Εκτυπώνει: Κλώνος Dolly
Σε αυτό το παράδειγμα, έχουμε την κλάση Provato
με μία ιδιότητα
$onoma
. Όταν κλωνοποιούμε ένα στιγμιότυπο αυτής της κλάσης, η
μέθοδος __clone()
φροντίζει ώστε το όνομα του κλωνοποιημένου
προβάτου να λάβει το πρόθεμα “Κλώνος”.
Traits
Τα Traits στην PHP είναι ένα εργαλείο που επιτρέπει την κοινή χρήση μεθόδων, ιδιοτήτων και σταθερών μεταξύ κλάσεων και την αποφυγή της διπλοτυπίας του κώδικα. Μπορείτε να τα φανταστείτε ως έναν μηχανισμό “αντιγραφής και επικόλλησης” (Ctrl-C και Ctrl-V), όπου το περιεχόμενο του trait “επικολλάται” στις κλάσεις. Αυτό σας επιτρέπει να επαναχρησιμοποιείτε κώδικα χωρίς την ανάγκη δημιουργίας πολύπλοκων ιεραρχιών κλάσεων.
Ας δείξουμε ένα απλό παράδειγμα για το πώς να χρησιμοποιείτε τα traits στην PHP:
trait Kornarisma
{
public function korna()
{
echo 'Μπιπ μπιπ!';
}
}
class Aftokinito
{
use Kornarisma;
}
class Fortigo
{
use Kornarisma;
}
$auto = new Aftokinito;
$auto->korna(); // Εκτυπώνει 'Μπιπ μπιπ!'
$nakladak = new Fortigo;
$nakladak->korna(); // Επίσης εκτυπώνει 'Μπιπ μπιπ!'
Σε αυτό το παράδειγμα, έχουμε ένα trait που ονομάζεται Kornarisma
, το
οποίο περιέχει μία μέθοδο korna()
. Στη συνέχεια, έχουμε δύο κλάσεις:
Aftokinito
και Fortigo
, οι οποίες και οι δύο χρησιμοποιούν το trait
Kornarisma
. Χάρη σε αυτό, και οι δύο κλάσεις “έχουν” τη μέθοδο
korna()
, και μπορούμε να την καλέσουμε σε αντικείμενα και των δύο
κλάσεων.
Τα Traits σας επιτρέπουν να μοιράζεστε εύκολα και αποτελεσματικά κώδικα
μεταξύ κλάσεων. Ταυτόχρονα, δεν εισέρχονται στην ιεραρχία
κληρονομικότητας, δηλαδή το $auto instanceof Kornarisma
θα επιστρέψει
false
.
Εξαιρέσεις
Οι εξαιρέσεις στον OOP μας επιτρέπουν να χειριζόμαστε κομψά σφάλματα και απροσδόκητες καταστάσεις στον κώδικά μας. Είναι αντικείμενα που φέρουν πληροφορίες σχετικά με το σφάλμα ή την ασυνήθιστη κατάσταση.
Στην PHP, έχουμε την ενσωματωμένη κλάση Exception
, η οποία
χρησιμεύει ως βάση για όλες τις εξαιρέσεις. Αυτή έχει αρκετές μεθόδους
που μας επιτρέπουν να λάβουμε περισσότερες πληροφορίες σχετικά με την
εξαίρεση, όπως το μήνυμα σφάλματος, το αρχείο και τη γραμμή όπου συνέβη
το σφάλμα, κ.λπ.
Όταν συμβεί ένα σφάλμα στον κώδικα, μπορούμε να “πετάξουμε” μια
εξαίρεση χρησιμοποιώντας τη λέξη-κλειδί throw
.
function diairesi(float $a, float $b): float
{
if ($b === 0.0) { // Σύγκριση με float, καλύτερα να χρησιμοποιήσετε === 0.0
throw new Exception('Διαίρεση με το μηδέν!');
}
return $a / $b;
}
Όταν η συνάρτηση diairesi()
λάβει μηδέν ως δεύτερο όρισμα, πετάει
μια εξαίρεση με το μήνυμα σφάλματος 'Διαίρεση με το μηδέν!'
. Για
να αποτρέψουμε την κατάρρευση του προγράμματος κατά την πρόκληση της
εξαίρεσης, την πιάνουμε σε ένα μπλοκ try/catch
:
try {
echo diairesi(10, 0);
} catch (Exception $e) {
echo 'Η εξαίρεση εντοπίστηκε: '. $e->getMessage();
}
Ο κώδικας που μπορεί να προκαλέσει μια εξαίρεση περικλείεται σε ένα
μπλοκ try
. Εάν προκληθεί μια εξαίρεση, η εκτέλεση του κώδικα
μετακινείται στο μπλοκ catch
, όπου μπορούμε να επεξεργαστούμε την
εξαίρεση (π.χ. να εμφανίσουμε ένα μήνυμα σφάλματος).
Μετά τα μπλοκ try
και catch
, μπορούμε να προσθέσουμε ένα
προαιρετικό μπλοκ finally
, το οποίο εκτελείται πάντα, ανεξάρτητα
από το αν προκλήθηκε εξαίρεση ή όχι (ακόμα και στην περίπτωση που στο
μπλοκ try
ή catch
χρησιμοποιήσουμε την εντολή return
,
break
ή continue
):
try {
echo diairesi(10, 0);
} catch (Exception $e) {
echo 'Η εξαίρεση εντοπίστηκε: '. $e->getMessage();
} finally {
// Κώδικας που εκτελείται πάντα, ανεξάρτητα από το αν προκλήθηκε εξαίρεση ή όχι
}
Μπορούμε επίσης να δημιουργήσουμε δικές μας κλάσεις (ιεραρχία) εξαιρέσεων, οι οποίες κληρονομούν από την κλάση Exception. Ως παράδειγμα, ας φανταστούμε μια απλή τραπεζική εφαρμογή που επιτρέπει την πραγματοποίηση καταθέσεων και αναλήψεων:
class TrapezikiExairesi extends Exception {}
class ExairesiAneparkousYpolipou extends TrapezikiExairesi {}
class ExairesiYpervasisOriou extends TrapezikiExairesi {}
class TrapezikosLogariasmos
{
private int $ypolipo = 0;
private int $imerisioOrio = 1000;
public function katathesi(int $poso): int
{
$this->ypolipo += $poso;
return $this->ypolipo;
}
public function analipsi(int $poso): int
{
if ($poso > $this->ypolipo) {
throw new ExairesiAneparkousYpolipou('Δεν υπάρχει επαρκές υπόλοιπο στον λογαριασμό.');
}
if ($poso > $this->imerisioOrio) {
throw new ExairesiYpervasisOriou('Έγινε υπέρβαση του ημερήσιου ορίου αναλήψεων.');
}
$this->ypolipo -= $poso;
return $this->ypolipo;
}
}
Για ένα μπλοκ try
, μπορούν να αναφερθούν πολλαπλά μπλοκ
catch
, εάν αναμένετε διαφορετικούς τύπους εξαιρέσεων.
$ucet = new TrapezikosLogariasmos;
$ucet->katathesi(500);
try {
$ucet->analipsi(1500);
} catch (ExairesiYpervasisOriou $e) {
echo $e->getMessage();
} catch (ExairesiAneparkousYpolipou $e) {
echo $e->getMessage();
} catch (TrapezikiExairesi $e) {
echo 'Παρουσιάστηκε σφάλμα κατά την εκτέλεση της λειτουργίας.';
}
Σε αυτό το παράδειγμα, είναι σημαντικό να σημειωθεί η σειρά των μπλοκ
catch
. Επειδή όλες οι εξαιρέσεις κληρονομούν από την
TrapezikiExairesi
, εάν είχαμε αυτό το μπλοκ πρώτο, θα πιάνονταν σε αυτό
όλες οι εξαιρέσεις, χωρίς ο κώδικας να φτάσει στα επόμενα μπλοκ
catch
. Γι' αυτό είναι σημαντικό να έχουμε τις πιο συγκεκριμένες
εξαιρέσεις (δηλαδή αυτές που κληρονομούν από άλλες) στο μπλοκ
catch
πιο πάνω στη σειρά από τις γονικές τους εξαιρέσεις.
Επανάληψη
Στην PHP, μπορείτε να διατρέξετε αντικείμενα χρησιμοποιώντας τον
βρόχο foreach
, παρόμοια με τον τρόπο που διατρέχετε πίνακες. Για να
λειτουργήσει αυτό, το αντικείμενο πρέπει να υλοποιεί μια ειδική
διεπαφή.
Η πρώτη επιλογή είναι να υλοποιήσετε τη διεπαφή Iterator
, η οποία
έχει τις μεθόδους current()
που επιστρέφει την τρέχουσα τιμή,
key()
που επιστρέφει το κλειδί, next()
που μετακινείται στην
επόμενη τιμή, rewind()
που μετακινείται στην αρχή και valid()
που ελέγχει αν δεν έχουμε φτάσει ακόμα στο τέλος.
Η δεύτερη επιλογή είναι να υλοποιήσετε τη διεπαφή IteratorAggregate
, η
οποία έχει μόνο μία μέθοδο getIterator()
. Αυτή είτε επιστρέφει ένα
αντικείμενο υποκατάστατο που θα εξασφαλίζει τη διάτρεξη, είτε μπορεί
να αντιπροσωπεύει μια γεννήτρια, η οποία είναι μια ειδική συνάρτηση
στην οποία χρησιμοποιείται το yield
για τη σταδιακή επιστροφή
κλειδιών και τιμών:
class Atomο
{
public function __construct(
public int $ilikia,
) {
}
}
class Lista implements IteratorAggregate
{
private array $atoma = [];
public function prosthikiAtomou(Atomο $atomo): void
{
$this->atoma[] = $atomo;
}
public function getIterator(): Generator
{
foreach ($this->atoma as $atomo) {
yield $atomo;
}
}
}
$seznam = new Lista;
$seznam->prosthikiAtomou(new Atomο(30));
$seznam->prosthikiAtomou(new Atomο(25));
foreach ($seznam as $atomo) {
echo "Ηλικία: {$atomo->ilikia} έτη \n";
}
Ορθές πρακτικές
Όταν έχετε κατανοήσει τις βασικές αρχές του αντικειμενοστραφούς προγραμματισμού, είναι σημαντικό να εστιάσετε στις ορθές πρακτικές στον OOP. Αυτές θα σας βοηθήσουν να γράψετε κώδικα που δεν είναι μόνο λειτουργικός, αλλά και ευανάγνωστος, κατανοητός και εύκολα συντηρήσιμος.
- Διαχωρισμός αρμοδιοτήτων (Separation of Concerns): Κάθε κλάση πρέπει να έχει μια σαφώς καθορισμένη ευθύνη και να επιλύει μόνο μία κύρια εργασία. Εάν μια κλάση κάνει πάρα πολλά πράγματα, μπορεί να είναι σκόπιμο να την χωρίσετε σε μικρότερες, εξειδικευμένες κλάσεις.
- Ενθυλάκωση (Encapsulation): Τα δεδομένα και οι μέθοδοι πρέπει να είναι όσο το δυνατόν πιο κρυμμένα και προσβάσιμα μόνο μέσω μιας καθορισμένης διεπαφής. Αυτό σας επιτρέπει να αλλάξετε την εσωτερική υλοποίηση της κλάσης χωρίς να επηρεάσετε τον υπόλοιπο κώδικα.
- Έγχυση εξαρτήσεων (Dependency Injection): Αντί να δημιουργείτε εξαρτήσεις απευθείας στην κλάση, θα πρέπει να τις “εγχέετε” από έξω. Για μια βαθύτερη κατανόηση αυτής της αρχής, συνιστούμε τα κεφάλαια για την Έγχυση Εξαρτήσεων.