/
introduction-to-object-oriented-programming.texy
841 lines (609 loc) · 49.4 KB
/
introduction-to-object-oriented-programming.texy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
Εισαγωγή στον αντικειμενοστραφή προγραμματισμό
**********************************************
.[perex]
Ο όρος "OOP" σημαίνει αντικειμενοστραφής προγραμματισμός, ο οποίος είναι ένας τρόπος οργάνωσης και δόμησης του κώδικα. Ο OOP μας επιτρέπει να βλέπουμε ένα πρόγραμμα ως μια συλλογή αντικειμένων που επικοινωνούν μεταξύ τους, αντί για μια ακολουθία εντολών και συναρτήσεων.
Στο OOP, ένα "αντικείμενο" είναι μια μονάδα που περιέχει δεδομένα και συναρτήσεις που λειτουργούν με αυτά τα δεδομένα. Τα αντικείμενα δημιουργούνται με βάση "κλάσεις", οι οποίες μπορούν να εκληφθούν ως σχέδια ή πρότυπα για αντικείμενα. Αφού έχουμε μια κλάση, μπορούμε να δημιουργήσουμε την "παρουσία" της, η οποία είναι ένα συγκεκριμένο αντικείμενο κατασκευασμένο από αυτή την κλάση.
Ας δούμε πώς μπορούμε να δημιουργήσουμε μια απλή κλάση στην PHP. Όταν ορίζουμε μια κλάση, χρησιμοποιούμε τη λέξη-κλειδί "class", ακολουθούμενη από το όνομα της κλάσης και στη συνέχεια από τις τεθλασμένες αγκύλες που περικλείουν τις συναρτήσεις της κλάσης (που ονομάζονται "μέθοδοι") και τις μεταβλητές της κλάσης (που ονομάζονται "ιδιότητες" ή "χαρακτηριστικά"):
```php
class Car
{
function honk()
{
echo 'Beep beep!';
}
}
```
Σε αυτό το παράδειγμα, δημιουργήσαμε μια κλάση με όνομα `Car` με μια συνάρτηση (ή "μέθοδο") που ονομάζεται `honk`.
Κάθε τάξη θα πρέπει να επιλύει μόνο μία κύρια εργασία. Εάν μια τάξη κάνει πάρα πολλά πράγματα, μπορεί να είναι σκόπιμο να χωριστεί σε μικρότερες, εξειδικευμένες τάξεις.
Οι κλάσεις συνήθως αποθηκεύονται σε ξεχωριστά αρχεία για να διατηρείται ο κώδικας οργανωμένος και εύκολος στην πλοήγηση. Το όνομα του αρχείου πρέπει να ταιριάζει με το όνομα της κλάσης, οπότε για την κλάση `Car`, το όνομα του αρχείου θα είναι `Car.php`.
Όταν ονομάζετε κλάσεις, είναι καλό να ακολουθείτε τη σύμβαση "PascalCase", δηλαδή κάθε λέξη στο όνομα αρχίζει με κεφαλαίο γράμμα και δεν υπάρχουν υπογραμμίσεις ή άλλα διαχωριστικά. Οι μέθοδοι και οι ιδιότητες ακολουθούν τη σύμβαση "camelCase", δηλαδή ξεκινούν με πεζά γράμματα.
Ορισμένες μέθοδοι στην PHP έχουν ειδικούς ρόλους και προτάσσονται με `__` (δύο υπογράμμιση). Μία από τις πιο σημαντικές ειδικές μεθόδους είναι ο "κατασκευαστής", με την ένδειξη `__construct`. Ο κατασκευαστής είναι μια μέθοδος που καλείται αυτόματα κατά τη δημιουργία μιας νέας περίπτωσης μιας κλάσης.
Συχνά χρησιμοποιούμε τον κατασκευαστή για να ορίσουμε την αρχική κατάσταση ενός αντικειμένου. Για παράδειγμα, κατά τη δημιουργία ενός αντικειμένου που αναπαριστά ένα άτομο, μπορεί να χρησιμοποιήσετε τον κατασκευαστή για να ορίσετε την ηλικία, το όνομα ή άλλα χαρακτηριστικά του.
Ας δούμε πώς μπορείτε να χρησιμοποιήσετε έναν κατασκευαστή στην PHP:
```php
class Person
{
private $age;
function __construct($age)
{
$this->age = $age;
}
function howOldAreYou()
{
return $this->age;
}
}
$person = new Person(25);
echo $person->howOldAreYou(); // Outputs: 25
```
Σε αυτό το παράδειγμα, η κλάση `Person` έχει μια ιδιότητα (μεταβλητή) `$age` και έναν κατασκευαστή που ορίζει αυτή την ιδιότητα. Η μέθοδος `howOldAreYou()` παρέχει στη συνέχεια πρόσβαση στην ηλικία του ατόμου.
Η ψευδομεταβλητή `$this` χρησιμοποιείται μέσα στην κλάση για την πρόσβαση στις ιδιότητες και τις μεθόδους του αντικειμένου.
Η λέξη-κλειδί `new` χρησιμοποιείται για τη δημιουργία μιας νέας περίπτωσης μιας κλάσης. Στο παραπάνω παράδειγμα, δημιουργήσαμε ένα νέο άτομο ηλικίας 25 ετών.
Μπορείτε επίσης να ορίσετε προεπιλεγμένες τιμές για τις παραμέτρους του κατασκευαστή, εάν δεν έχουν καθοριστεί κατά τη δημιουργία ενός αντικειμένου. Για παράδειγμα:
```php
class Person
{
private $age;
function __construct($age = 20)
{
$this->age = $age;
}
function howOldAreYou()
{
return $this->age;
}
}
$person = new Person; // if no argument is passed, parentheses can be omitted
echo $person->howOldAreYou(); // Outputs: 20
```
Σε αυτό το παράδειγμα, αν δεν καθορίσετε μια ηλικία κατά τη δημιουργία ενός αντικειμένου `Person`, θα χρησιμοποιηθεί η προεπιλεγμένη τιμή 20.
Το ωραίο είναι ότι ο ορισμός της ιδιότητας με την αρχικοποίησή της μέσω του κατασκευαστή μπορεί να συντομευτεί και να απλοποιηθεί ως εξής:
```php
class Person
{
function __construct(
private $age = 20,
) {
}
}
```
Για λόγους πληρότητας, εκτός από τους κατασκευαστές, τα αντικείμενα μπορούν να έχουν καταστροφείς (μέθοδος `__destruct`) που καλούνται πριν από την αποδέσμευση του αντικειμένου από τη μνήμη.
Χώροι ονομάτων .[#toc-namespaces]
---------------------------------
Οι χώροι ονομάτων μας επιτρέπουν να οργανώνουμε και να ομαδοποιούμε συναφείς κλάσεις, συναρτήσεις και σταθερές αποφεύγοντας συγκρούσεις ονομάτων. Μπορείτε να τα φανταστείτε όπως τους φακέλους σε έναν υπολογιστή, όπου κάθε φάκελος περιέχει αρχεία που σχετίζονται με ένα συγκεκριμένο έργο ή θέμα.
Οι χώροι ονομάτων είναι ιδιαίτερα χρήσιμοι σε μεγαλύτερα έργα ή όταν χρησιμοποιείτε βιβλιοθήκες τρίτων, όπου μπορεί να προκύψουν συγκρούσεις ονοματοδοσίας κλάσεων.
Φανταστείτε ότι έχετε μια κλάση με όνομα `Car` στο έργο σας και θέλετε να την τοποθετήσετε σε ένα χώρο ονομάτων με όνομα `Transport`. Θα το κάνατε ως εξής:
```php
namespace Transport;
class Car
{
function honk()
{
echo 'Beep beep!';
}
}
```
Αν θέλετε να χρησιμοποιήσετε την κλάση `Car` σε ένα άλλο αρχείο, πρέπει να καθορίσετε από ποιο χώρο ονομάτων προέρχεται η κλάση:
```php
$car = new Transport\Car;
```
Για λόγους απλούστευσης, μπορείτε να καθορίσετε στην αρχή του αρχείου ποια κλάση από ένα συγκεκριμένο χώρο ονομάτων θέλετε να χρησιμοποιήσετε, επιτρέποντάς σας να δημιουργήσετε περιπτώσεις χωρίς να αναφέρετε την πλήρη διαδρομή:
```php
use Transport\Car;
$car = new Car;
```
Κληρονομικότητα .[#toc-inheritance]
-----------------------------------
Η κληρονομικότητα είναι ένα εργαλείο του αντικειμενοστρεφούς προγραμματισμού που επιτρέπει τη δημιουργία νέων κλάσεων με βάση τις υπάρχουσες, κληρονομώντας τις ιδιότητες και τις μεθόδους τους και επεκτείνοντας ή επαναπροσδιορίζοντας τες ανάλογα με τις ανάγκες. Η κληρονομικότητα εξασφαλίζει την επαναχρησιμοποίηση του κώδικα και την ιεραρχία των κλάσεων.
Με απλά λόγια, αν έχουμε μια κλάση και θέλουμε να δημιουργήσουμε μια άλλη που να προέρχεται από αυτήν αλλά με κάποιες τροποποιήσεις, μπορούμε να "κληρονομήσουμε" τη νέα κλάση από την αρχική.
Στην PHP, η κληρονομικότητα υλοποιείται με τη χρήση της λέξης-κλειδί `extends`.
Η κλάση μας `Person` αποθηκεύει πληροφορίες για την ηλικία. Μπορούμε να έχουμε μια άλλη κλάση, την `Student`, η οποία επεκτείνει την `Person` και προσθέτει πληροφορίες σχετικά με τον τομέα σπουδών.
Ας δούμε ένα παράδειγμα:
```php
class Person
{
private $age;
function __construct($age)
{
$this->age = $age;
}
function printInformation()
{
echo "Age: {$this->age} years\n";
}
}
class Student extends Person
{
private $fieldOfStudy;
function __construct($age, $fieldOfStudy)
{
parent::__construct($age);
$this->fieldOfStudy = $fieldOfStudy;
}
function printInformation()
{
parent::printInformation();
echo "Field of study: {$this->fieldOfStudy} \n";
}
}
$student = new Student(20, 'Computer Science');
$student->printInformation();
```
Πώς λειτουργεί αυτός ο κώδικας;
- Χρησιμοποιήσαμε τη λέξη-κλειδί `extends` για να επεκτείνουμε την κλάση `Person`, δηλαδή η κλάση `Student` κληρονομεί όλες τις μεθόδους και τις ιδιότητες από την `Person`.
- Η λέξη-κλειδί `parent::` μας επιτρέπει να καλούμε μεθόδους από τη γονική κλάση. Σε αυτή την περίπτωση, καλέσαμε τον κατασκευαστή από την κλάση `Person` πριν προσθέσουμε τη δική μας λειτουργικότητα στην κλάση `Student`. Και ομοίως, τη μέθοδο του προγόνου `printInformation()` πριν καταχωρίσουμε τις πληροφορίες του μαθητή.
Η κληρονομικότητα προορίζεται για καταστάσεις όπου υπάρχει μια σχέση "είναι ένα" μεταξύ κλάσεων. Για παράδειγμα, μια `Student` είναι μια `Person`. Μια γάτα είναι ένα ζώο. Μας επιτρέπει σε περιπτώσεις όπου περιμένουμε ένα αντικείμενο (π.χ. "Person") στον κώδικα να χρησιμοποιήσουμε αντί αυτού ένα παράγωγο αντικείμενο (π.χ. "Student").
Είναι σημαντικό να συνειδητοποιήσουμε ότι ο πρωταρχικός σκοπός της κληρονομικότητας **δεν** είναι να αποτρέψει την επανάληψη του κώδικα. Αντιθέτως, η κακή χρήση της κληρονομικότητας μπορεί να οδηγήσει σε πολύπλοκο και δύσκολα συντηρήσιμο κώδικα. Εάν δεν υπάρχει σχέση "είναι ένα" μεταξύ των κλάσεων, θα πρέπει να εξετάσουμε τη σύνθεση αντί της κληρονομικότητας.
Σημειώστε ότι οι μέθοδοι `printInformation()` στις κλάσεις `Person` και `Student` δίνουν ελαφρώς διαφορετικές πληροφορίες. Και μπορούμε να προσθέσουμε άλλες κλάσεις (όπως η `Employee`) που θα παρέχουν άλλες υλοποιήσεις αυτής της μεθόδου. Η ικανότητα των αντικειμένων διαφορετικών κλάσεων να ανταποκρίνονται στην ίδια μέθοδο με διαφορετικούς τρόπους ονομάζεται πολυμορφισμός:
```php
$people = [
new Person(30),
new Student(20, 'Computer Science'),
new Employee(45, 'Director'),
];
foreach ($people as $person) {
$person->printInformation();
}
```
Σύνθεση .[#toc-composition]
---------------------------
Η σύνθεση είναι μια τεχνική κατά την οποία, αντί να κληρονομούμε ιδιότητες και μεθόδους από μια άλλη κλάση, απλά χρησιμοποιούμε την εμφάνισή της στην κλάση μας. Αυτό μας επιτρέπει να συνδυάζουμε λειτουργίες και ιδιότητες πολλών κλάσεων χωρίς να δημιουργούμε πολύπλοκες δομές κληρονομικότητας.
Για παράδειγμα, έχουμε μια κλάση `Engine` και μια κλάση `Car`. Αντί να πούμε "Ένα αυτοκίνητο είναι ένας κινητήρας", λέμε "Ένα αυτοκίνητο έχει έναν κινητήρα", που είναι μια τυπική σχέση σύνθεσης.
```php
class Engine
{
function start()
{
echo 'Engine is running.';
}
}
class Car
{
private $engine;
function __construct()
{
$this->engine = new Engine;
}
function start()
{
$this->engine->start();
echo 'The car is ready to drive!';
}
}
$car = new Car;
$car->start();
```
Εδώ, η `Car` δεν έχει όλες τις ιδιότητες και τις μεθόδους της `Engine`, αλλά έχει πρόσβαση σε αυτές μέσω της ιδιότητας `$engine`.
Το πλεονέκτημα της σύνθεσης είναι η μεγαλύτερη ευελιξία σχεδιασμού και η καλύτερη προσαρμοστικότητα για μελλοντικές αλλαγές.
Ορατότητα .[#toc-visibility]
----------------------------
Στην PHP, μπορείτε να ορίσετε "ορατότητα" για ιδιότητες, μεθόδους και σταθερές κλάσεων. Η ορατότητα καθορίζει πού μπορείτε να έχετε πρόσβαση σε αυτά τα στοιχεία.
1. **Δημόσιο:** Εάν ένα στοιχείο χαρακτηρίζεται ως `public`, σημαίνει ότι μπορείτε να έχετε πρόσβαση σε αυτό από οπουδήποτε, ακόμη και εκτός της κλάσης.
2. **Προστατευμένο:** Ένα στοιχείο που επισημαίνεται ως `protected` είναι προσβάσιμο μόνο μέσα στην κλάση και σε όλους τους απογόνους της (κλάσεις που κληρονομούν από αυτήν).
3. **Ιδιωτικό:** Εάν ένα στοιχείο είναι `private`, μπορείτε να έχετε πρόσβαση σε αυτό μόνο μέσα από την κλάση στην οποία ορίστηκε.
Αν δεν καθορίσετε ορατότητα, η PHP θα την ορίσει αυτόματα σε `public`.
Ας δούμε ένα παράδειγμα κώδικα:
```php
class VisibilityExample
{
public $publicProperty = 'Public';
protected $protectedProperty = 'Protected';
private $privateProperty = 'Private';
public function printProperties()
{
echo $this->publicProperty; // Works
echo $this->protectedProperty; // Works
echo $this->privateProperty; // Works
}
}
$object = new VisibilityExample;
$object->printProperties();
echo $object->publicProperty; // Works
// echo $object->protectedProperty; // Throws an error
// echo $object->privateProperty; // Throws an error
```
Συνεχίζοντας με την κληρονομικότητα των κλάσεων:
```php
class ChildClass extends VisibilityExample
{
public function printProperties()
{
echo $this->publicProperty; // Works
echo $this->protectedProperty; // Works
// echo $this->privateProperty; // Throws an error
}
}
```
Σε αυτή την περίπτωση, η μέθοδος `printProperties()` στην `ChildClass` μπορεί να έχει πρόσβαση στις δημόσιες και προστατευόμενες ιδιότητες, αλλά δεν μπορεί να έχει πρόσβαση στις ιδιωτικές ιδιότητες της γονικής κλάσης.
Τα δεδομένα και οι μέθοδοι θα πρέπει να είναι όσο το δυνατόν πιο κρυφά και προσβάσιμα μόνο μέσω μιας καθορισμένης διεπαφής. Αυτό σας επιτρέπει να αλλάξετε την εσωτερική υλοποίηση της κλάσης χωρίς να επηρεάζεται ο υπόλοιπος κώδικας.
Λέξη-κλειδί Final .[#toc-final-keyword]
---------------------------------------
Στην PHP, μπορούμε να χρησιμοποιήσουμε τη λέξη-κλειδί `final` αν θέλουμε να αποτρέψουμε μια κλάση, μέθοδο ή σταθερά από το να κληρονομηθεί ή να αντικατασταθεί. Όταν μια κλάση χαρακτηρίζεται ως `final`, δεν μπορεί να επεκταθεί. Όταν μια μέθοδος χαρακτηρίζεται ως `final`, δεν μπορεί να αντικατασταθεί σε μια υποκλάση.
Γνωρίζοντας ότι μια συγκεκριμένη κλάση ή μέθοδος δεν θα τροποποιείται πλέον, μπορούμε να κάνουμε αλλαγές πιο εύκολα χωρίς να ανησυχούμε για πιθανές συγκρούσεις. Για παράδειγμα, μπορούμε να προσθέσουμε μια νέα μέθοδο χωρίς να φοβόμαστε ότι ένας απόγονος μπορεί να έχει ήδη μια μέθοδο με το ίδιο όνομα, οδηγώντας σε σύγκρουση. Ή μπορούμε να αλλάξουμε τις παραμέτρους μιας μεθόδου, και πάλι χωρίς τον κίνδυνο να προκαλέσουμε ασυνέπεια με μια μέθοδο που έχει παρακαμφθεί σε έναν απόγονο.
```php
final class FinalClass
{
}
// The following code will throw an error because we cannot inherit from a final class.
class ChildOfFinalClass extends FinalClass
{
}
```
Σε αυτό το παράδειγμα, η απόπειρα κληρονόμησης από την τελική κλάση `FinalClass` θα οδηγήσει σε σφάλμα.
Στατικές ιδιότητες και μέθοδοι .[#toc-static-properties-and-methods]
--------------------------------------------------------------------
Όταν μιλάμε για "στατικά" στοιχεία μιας κλάσης στην PHP, εννοούμε μεθόδους και ιδιότητες που ανήκουν στην ίδια την κλάση και όχι σε μια συγκεκριμένη περίπτωση της κλάσης. Αυτό σημαίνει ότι δεν χρειάζεται να δημιουργήσετε μια παρουσία της κλάσης για να αποκτήσετε πρόσβαση σε αυτά. Αντ' αυτού, τις καλείτε ή τις προσπελαύνετε απευθείας μέσω του ονόματος της κλάσης.
Λάβετε υπόψη ότι, εφόσον τα στατικά στοιχεία ανήκουν στην κλάση και όχι στις περιπτώσεις της, δεν μπορείτε να χρησιμοποιήσετε την ψευδομεταβλητή `$this` μέσα σε στατικές μεθόδους.
Η χρήση στατικών ιδιοτήτων οδηγεί σε [συγκεχυμένο κώδικα γεμάτο παγίδες |dependency-injection:global-state], γι' αυτό δεν πρέπει ποτέ να τις χρησιμοποιείτε και δεν θα δείξουμε ένα παράδειγμα εδώ. Από την άλλη πλευρά, οι στατικές μέθοδοι είναι χρήσιμες. Ακολουθεί ένα παράδειγμα:
```php
class Calculator
{
public static function add($a, $b)
{
return $a + $b;
}
public static function subtract($a, $b)
{
return $a - $b;
}
}
// Using the static method without creating an instance of the class
echo Calculator::add(5, 3); // Output: 8
echo Calculator::subtract(5, 3); // Output: 2
```
Σε αυτό το παράδειγμα, δημιουργήσαμε μια κλάση `Calculator` με δύο στατικές μεθόδους. Μπορούμε να καλέσουμε αυτές τις μεθόδους απευθείας χωρίς να δημιουργήσουμε μια περίπτωση της κλάσης χρησιμοποιώντας τον τελεστή `::`. Οι στατικές μέθοδοι είναι ιδιαίτερα χρήσιμες για λειτουργίες που δεν εξαρτώνται από την κατάσταση μιας συγκεκριμένης περίπτωσης της κλάσης.
Σταθερές κλάσης .[#toc-class-constants]
---------------------------------------
Μέσα στις κλάσεις, έχουμε τη δυνατότητα να ορίσουμε σταθερές. Οι σταθερές είναι τιμές που δεν αλλάζουν ποτέ κατά τη διάρκεια της εκτέλεσης του προγράμματος. Σε αντίθεση με τις μεταβλητές, η τιμή μιας σταθεράς παραμένει η ίδια.
```php
class Car
{
public const NumberOfWheels = 4;
public function displayNumberOfWheels(): int
{
echo self::NumberOfWheels;
}
}
echo Car::NumberOfWheels; // Output: 4
```
Σε αυτό το παράδειγμα, έχουμε μια κλάση `Car` με τη σταθερά `NumberOfWheels`. Όταν έχουμε πρόσβαση στη σταθερά μέσα στην κλάση, μπορούμε να χρησιμοποιήσουμε τη λέξη-κλειδί `self` αντί για το όνομα της κλάσης.
Διεπαφές αντικειμένων .[#toc-object-interfaces]
-----------------------------------------------
Οι διεπαφές αντικειμένων λειτουργούν ως "συμβόλαια" για τις κλάσεις. Εάν μια κλάση πρόκειται να υλοποιήσει μια διεπαφή αντικειμένου, πρέπει να περιέχει όλες τις μεθόδους που ορίζει η διεπαφή. Είναι ένας πολύ καλός τρόπος για να διασφαλιστεί ότι ορισμένες κλάσεις τηρούν το ίδιο "συμβόλαιο" ή την ίδια δομή.
Στην PHP, οι διεπαφές ορίζονται χρησιμοποιώντας τη λέξη-κλειδί `interface`. Όλες οι μέθοδοι που ορίζονται σε μια διεπαφή είναι δημόσιες (`public`). Όταν μια κλάση υλοποιεί μια διεπαφή, χρησιμοποιεί τη λέξη-κλειδί `implements`.
```php
interface Animal
{
function makeSound();
}
class Cat implements Animal
{
public function makeSound()
{
echo 'Meow';
}
}
$cat = new Cat;
$cat->makeSound();
```
Εάν μια κλάση υλοποιεί μια διασύνδεση, αλλά δεν έχουν οριστεί όλες οι αναμενόμενες μέθοδοι, η PHP θα εκπέμψει ένα σφάλμα.
Μια κλάση μπορεί να υλοποιήσει πολλές διεπαφές ταυτόχρονα, κάτι που διαφέρει από την κληρονομικότητα, όπου μια κλάση μπορεί να κληρονομήσει μόνο από μια κλάση:
```php
interface Guardian
{
function guardHouse();
}
class Dog implements Animal, Guardian
{
public function makeSound()
{
echo 'Bark';
}
public function guardHouse()
{
echo 'Dog diligently guards the house';
}
}
```
Αφηρημένες κλάσεις .[#toc-abstract-classes]
-------------------------------------------
Οι αφηρημένες κλάσεις χρησιμεύουν ως πρότυπα βάσης για άλλες κλάσεις, αλλά δεν μπορείτε να δημιουργήσετε άμεσα τις περιπτώσεις τους. Περιέχουν ένα μείγμα από πλήρεις μεθόδους και αφηρημένες μεθόδους που δεν έχουν καθορισμένο περιεχόμενο. Οι κλάσεις που κληρονομούν από αφηρημένες κλάσεις πρέπει να παρέχουν ορισμούς για όλες τις αφηρημένες μεθόδους του γονέα.
Χρησιμοποιούμε τη λέξη-κλειδί `abstract` για να ορίσουμε μια αφηρημένη κλάση.
```php
abstract class AbstractClass
{
public function regularMethod()
{
echo 'This is a regular method';
}
abstract public function abstractMethod();
}
class Child extends AbstractClass
{
public function abstractMethod()
{
echo 'This is the implementation of the abstract method';
}
}
$instance = new Child;
$instance->regularMethod();
$instance->abstractMethod();
```
Σε αυτό το παράδειγμα, έχουμε μια αφηρημένη κλάση με μια κανονική και μια αφηρημένη μέθοδο. Στη συνέχεια έχουμε μια κλάση `Child` που κληρονομεί από την `AbstractClass` και παρέχει μια υλοποίηση για την αφηρημένη μέθοδο.
Σε τι διαφέρουν οι διεπαφές και οι αφηρημένες κλάσεις; Οι αφηρημένες κλάσεις μπορούν να περιέχουν τόσο αφηρημένες όσο και συγκεκριμένες μεθόδους, ενώ οι διεπαφές ορίζουν μόνο ποιες μεθόδους πρέπει να υλοποιεί η κλάση, αλλά δεν παρέχουν καμία υλοποίηση. Μια κλάση μπορεί να κληρονομήσει από μία μόνο αφηρημένη κλάση, αλλά μπορεί να υλοποιήσει οποιονδήποτε αριθμό διεπαφών.
Έλεγχος τύπου .[#toc-type-checking]
-----------------------------------
Στον προγραμματισμό, είναι ζωτικής σημασίας να διασφαλίζουμε ότι τα δεδομένα με τα οποία εργαζόμαστε έχουν τον σωστό τύπο. Στην PHP, διαθέτουμε εργαλεία που παρέχουν αυτή τη διασφάλιση. Η επαλήθευση ότι τα δεδομένα είναι σωστού τύπου ονομάζεται "έλεγχος τύπου".
Τύποι που μπορεί να συναντήσουμε στην PHP:
1. **Βασικοί τύποι**: Αυτοί περιλαμβάνουν `int` (ακέραιοι αριθμοί), `float` (αριθμοί κινητής υποδιαστολής), `bool` (τιμές boolean), `string` (συμβολοσειρές), `array` (πίνακες) και `null`.
2. **Κλάσεις**: Όταν θέλουμε μια τιμή να είναι περίπτωση μιας συγκεκριμένης κλάσης.
3. **Διασυνδέσεις**: Ορίζει ένα σύνολο μεθόδων που πρέπει να υλοποιεί μια κλάση. Μια τιμή που ανταποκρίνεται σε μια διεπαφή πρέπει να έχει αυτές τις μεθόδους.
4. **Μεικτοί τύποι**: Μπορούμε να καθορίσουμε ότι μια μεταβλητή μπορεί να έχει πολλούς επιτρεπόμενους τύπους.
5. **Void**: Αυτός ο ειδικός τύπος δηλώνει ότι μια συνάρτηση ή μέθοδος δεν επιστρέφει καμία τιμή.
Ας δούμε πώς μπορούμε να τροποποιήσουμε τον κώδικα για να συμπεριλάβουμε τύπους:
```php
class Person
{
private int $age;
public function __construct(int $age)
{
$this->age = $age;
}
public function printAge(): void
{
echo "This person is {$this->age} years old.";
}
}
/**
* A function that accepts a Person object and prints the person's age.
*/
function printPersonAge(Person $person): void
{
$person->printAge();
}
```
Με αυτόν τον τρόπο εξασφαλίζουμε ότι ο κώδικάς μας αναμένει και λειτουργεί με δεδομένα του σωστού τύπου, βοηθώντας μας να αποφύγουμε πιθανά σφάλματα.
Ορισμένοι τύποι δεν μπορούν να γραφτούν απευθείας σε PHP. Σε αυτή την περίπτωση, αναφέρονται στο σχόλιο phpDoc, το οποίο είναι η τυπική μορφή για την τεκμηρίωση κώδικα PHP, ξεκινώντας με `/**` και τελειώνοντας με `*/`. Σας επιτρέπει να προσθέσετε περιγραφές κλάσεων, μεθόδων κ.λπ. Και επίσης να απαριθμήσετε σύνθετους τύπους χρησιμοποιώντας τις λεγόμενες επισημειώσεις `@var`, `@param` και `@return`. Αυτοί οι τύποι χρησιμοποιούνται στη συνέχεια από εργαλεία στατικής ανάλυσης κώδικα, αλλά δεν ελέγχονται από την ίδια την PHP.
```php
class Registry
{
/** @var array<Person> indicates that it's an array of Person objects */
private array $persons = [];
public function addPerson(Person $person): void
{
$this->persons[] = $person;
}
}
```
Σύγκριση και ταυτότητα .[#toc-comparison-and-identity]
------------------------------------------------------
Στην PHP, μπορείτε να συγκρίνετε αντικείμενα με δύο τρόπους:
1. Σύγκριση τιμών `==`: Ελέγχει αν τα αντικείμενα ανήκουν στην ίδια κλάση και έχουν τις ίδιες τιμές στις ιδιότητές τους.
2. Σύγκριση ταυτότητας `===`: Ελέγχει αν πρόκειται για την ίδια περίπτωση του αντικειμένου.
```php
class Car
{
public string $brand;
public function __construct(string $brand)
{
$this->brand = $brand;
}
}
$car1 = new Car('Skoda');
$car2 = new Car('Skoda');
$car3 = $car1;
var_dump($car1 == $car2); // true, because they have the same value
var_dump($car1 === $car2); // false, because they are not the same instance
var_dump($car1 === $car3); // true, because $car3 is the same instance as $car1
```
Ο χειριστής `instanceof` .[#toc-the-instanceof-operator]
--------------------------------------------------------
Ο τελεστής `instanceof` σας επιτρέπει να προσδιορίσετε αν ένα δεδομένο αντικείμενο είναι παράδειγμα μιας συγκεκριμένης κλάσης, απόγονος αυτής της κλάσης ή αν υλοποιεί μια συγκεκριμένη διασύνδεση.
Φανταστείτε ότι έχουμε μια κλάση `Person` και μια άλλη κλάση `Student`, η οποία είναι απόγονος της `Person`:
```php
class Person
{
private int $age;
public function __construct(int $age)
{
$this->age = $age;
}
}
class Student extends Person
{
private string $major;
public function __construct(int $age, string $major)
{
parent::__construct($age);
$this->major = $major;
}
}
$student = new Student(20, 'Computer Science');
// Check if $student is an instance of the Student class
var_dump($student instanceof Student); // Output: bool(true)
// Check if $student is an instance of the Person class (because Student is a descendant of Person)
var_dump($student instanceof Person); // Output: bool(true)
```
Από τις εξόδους, είναι προφανές ότι το αντικείμενο `$student` θεωρείται παράδειγμα και των δύο κλάσεων `Student` και `Person`.
Ρευστές διεπαφές .[#toc-fluent-interfaces]
------------------------------------------
Μια "ρευστή διεπαφή" είναι μια τεχνική στην OOP που επιτρέπει την αλυσιδωτή σύνδεση μεθόδων με μία μόνο κλήση. Αυτό συχνά απλοποιεί και αποσαφηνίζει τον κώδικα.
Το βασικό στοιχείο μιας ρευστής διεπαφής είναι ότι κάθε μέθοδος στην αλυσίδα επιστρέφει μια αναφορά στο τρέχον αντικείμενο. Αυτό επιτυγχάνεται με τη χρήση του `return $this;` στο τέλος της μεθόδου. Αυτό το στυλ προγραμματισμού συνδέεται συχνά με μεθόδους που ονομάζονται "setters", οι οποίες ορίζουν τις τιμές των ιδιοτήτων ενός αντικειμένου.
Ας δούμε πώς θα μπορούσε να μοιάζει μια ρευστή διεπαφή για την αποστολή μηνυμάτων ηλεκτρονικού ταχυδρομείου:
```php
public function sendMessage()
{
$email = new Email;
$email->setFrom('sender@example.com')
->setRecipient('admin@example.com')
->setMessage('Hello, this is a message.')
->send();
}
```
Σε αυτό το παράδειγμα, οι μέθοδοι `setFrom()`, `setRecipient()` και `setMessage()` χρησιμοποιούνται για να ορίσουν τις αντίστοιχες τιμές (αποστολέας, παραλήπτης, περιεχόμενο μηνύματος). Μετά τον ορισμό καθεμιάς από αυτές τις τιμές, οι μέθοδοι επιστρέφουν το τρέχον αντικείμενο (`$email`), επιτρέποντάς μας να ακολουθήσουμε αλυσιδωτά μια άλλη μέθοδο. Τέλος, καλούμε τη μέθοδο `send()`, η οποία στην πραγματικότητα στέλνει το μήνυμα ηλεκτρονικού ταχυδρομείου.
Χάρη στις ρευστές διεπαφές, μπορούμε να γράψουμε κώδικα που είναι διαισθητικός και εύκολα αναγνώσιμος.
Αντιγραφή με `clone` .[#toc-copying-with-clone]
-----------------------------------------------
Στην PHP, μπορούμε να δημιουργήσουμε ένα αντίγραφο ενός αντικειμένου χρησιμοποιώντας τον τελεστή `clone`. Με αυτόν τον τρόπο, λαμβάνουμε μια νέα περίπτωση με πανομοιότυπο περιεχόμενο.
Αν χρειάζεται να τροποποιήσουμε κάποιες από τις ιδιότητές του κατά την αντιγραφή ενός αντικειμένου, μπορούμε να ορίσουμε μια ειδική μέθοδο `__clone()` στην κλάση. Αυτή η μέθοδος καλείται αυτόματα όταν το αντικείμενο κλωνοποιείται.
```php
class Sheep
{
public string $name;
public function __construct(string $name)
{
$this->name = $name;
}
public function __clone()
{
$this->name = 'Clone of ' . $this->name;
}
}
$original = new Sheep('Dolly');
echo $original->name . "\n"; // Outputs: Dolly
$clone = clone $original;
echo $clone->name . "\n"; // Outputs: Clone of Dolly
```
Σε αυτό το παράδειγμα, έχουμε μια κλάση `Sheep` με μια ιδιότητα `$name`. Όταν κλωνοποιούμε μια περίπτωση αυτής της κλάσης, η μέθοδος `__clone()` εξασφαλίζει ότι το όνομα του κλωνοποιημένου προβάτου παίρνει το πρόθεμα "Clone of".
Χαρακτηριστικά .[#toc-traits]
-----------------------------
Τα γνωρίσματα στην PHP είναι ένα εργαλείο που επιτρέπει την κοινή χρήση μεθόδων, ιδιοτήτων και σταθερών μεταξύ κλάσεων και αποτρέπει την επανάληψη κώδικα. Μπορείτε να τα φανταστείτε ως ένα μηχανισμό "αντιγραφής και επικόλλησης" (Ctrl-C και Ctrl-V), όπου το περιεχόμενο ενός γνωρίσματος "επικολλάται" σε κλάσεις. Αυτό σας επιτρέπει να επαναχρησιμοποιείτε κώδικα χωρίς να χρειάζεται να δημιουργείτε περίπλοκες ιεραρχίες κλάσεων.
Ας ρίξουμε μια ματιά σε ένα απλό παράδειγμα χρήσης των traits στην PHP:
```php
trait Honking
{
public function honk()
{
echo 'Beep beep!';
}
}
class Car
{
use Honking;
}
class Truck
{
use Honking;
}
$car = new Car;
$car->honk(); // Outputs 'Beep beep!'
$truck = new Truck;
$truck->honk(); // Also outputs 'Beep beep!'
```
Σε αυτό το παράδειγμα, έχουμε ένα trait με όνομα `Honking` που περιέχει μία μέθοδο `honk()`. Στη συνέχεια, έχουμε δύο κλάσεις: `Car` και `Truck`, οι οποίες χρησιμοποιούν το χαρακτηριστικό `Honking`. Ως αποτέλεσμα, και οι δύο κλάσεις "έχουν" τη μέθοδο `honk()` και μπορούμε να την καλέσουμε σε αντικείμενα και των δύο κλάσεων.
Τα γνωρίσματα σας επιτρέπουν να μοιράζεστε εύκολα και αποτελεσματικά κώδικα μεταξύ κλάσεων. Δεν εισέρχονται στην ιεραρχία κληρονομικότητας, δηλαδή το `$car instanceof Honking` θα επιστρέψει το `false`.
Εξαιρέσεις
----------
Οι εξαιρέσεις στην OOP μας επιτρέπουν να χειριζόμαστε με αξιοπρέπεια σφάλματα και απροσδόκητες καταστάσεις στον κώδικά μας. Πρόκειται για αντικείμενα που μεταφέρουν πληροφορίες σχετικά με ένα σφάλμα ή μια ασυνήθιστη κατάσταση.
Στην PHP, έχουμε μια ενσωματωμένη κλάση `Exception`, η οποία χρησιμεύει ως βάση για όλες τις εξαιρέσεις. Αυτή διαθέτει διάφορες μεθόδους που μας επιτρέπουν να λάβουμε περισσότερες πληροφορίες σχετικά με την εξαίρεση, όπως το μήνυμα σφάλματος, το αρχείο και τη γραμμή όπου συνέβη το σφάλμα κ.λπ.
Όταν εμφανίζεται ένα σφάλμα στον κώδικα, μπορούμε να "πετάξουμε" την εξαίρεση χρησιμοποιώντας τη λέξη-κλειδί `throw`.
```php
function division(float $a, float $b): float
{
if ($b === 0) {
throw new Exception('Division by zero!');
}
return $a / $b;
}
```
Όταν η συνάρτηση `division()` λαμβάνει null ως δεύτερο όρισμά της, εκπέμπει μια εξαίρεση με το μήνυμα σφάλματος `'Division by zero!'`. Για να αποτρέψουμε τη συντριβή του προγράμματος όταν εκπέμπεται η εξαίρεση, την παγιδεύουμε στο μπλοκ `try/catch`:
```php
try {
echo division(10, 0);
} catch (Exception $e) {
echo 'Exception caught: '. $e->getMessage();
}
```
Ο κώδικας που μπορεί να πετάξει μια εξαίρεση τυλίγεται σε ένα μπλοκ `try`. Αν η εξαίρεση εκτοξευτεί, η εκτέλεση του κώδικα μεταφέρεται σε ένα μπλοκ `catch`, όπου μπορούμε να χειριστούμε την εξαίρεση (π.χ. να γράψουμε ένα μήνυμα σφάλματος).
Μετά τα μπλοκ `try` και `catch`, μπορούμε να προσθέσουμε ένα προαιρετικό μπλοκ `finally`, το οποίο εκτελείται πάντα, ανεξάρτητα από το αν η εξαίρεση προκλήθηκε ή όχι (ακόμη και αν χρησιμοποιήσουμε τα μπλοκ `return`, `break` ή `continue` στο μπλοκ `try` ή `catch` ):
```php
try {
echo division(10, 0);
} catch (Exception $e) {
echo 'Exception caught: '. $e->getMessage();
} finally {
// Code that is always executed whether the exception has been thrown or not
}
```
Μπορούμε επίσης να δημιουργήσουμε τις δικές μας κλάσεις εξαιρέσεων (ιεραρχία) που κληρονομούν από την κλάση Exception. Ως παράδειγμα, θεωρήστε μια απλή τραπεζική εφαρμογή που επιτρέπει καταθέσεις και αναλήψεις:
```php
class BankingException extends Exception {}
class InsufficientFundsException extends BankingException {}
class ExceededLimitException extends BankingException {}
class BankAccount
{
private int $balance = 0;
private int $dailyLimit = 1000;
public function deposit(int $amount): int
{
$this->balance += $amount;
return $this->balance;
}
public function withdraw(int $amount): int
{
if ($amount > $this->balance) {
throw new InsufficientFundsException('Not enough funds in the account.');
}
if ($amount > $this->dailyLimit) {
throw new ExceededLimitException('Daily withdrawal limit exceeded.');
}
$this->balance -= $amount;
return $this->balance;
}
}
```
Μπορούν να καθοριστούν πολλαπλά μπλοκ `catch` για ένα μόνο μπλοκ `try`, εάν περιμένετε διαφορετικούς τύπους εξαιρέσεων.
```php
$account = new BankAccount;
$account->deposit(500);
try {
$account->withdraw(1500);
} catch (ExceededLimitException $e) {
echo $e->getMessage();
} catch (InsufficientFundsException $e) {
echo $e->getMessage();
} catch (BankingException $e) {
echo 'An error occurred during the operation.';
}
```
Σε αυτό το παράδειγμα, είναι σημαντικό να σημειωθεί η σειρά των μπλοκ `catch`. Δεδομένου ότι όλες οι εξαιρέσεις κληρονομούν από το `BankingException`, αν είχαμε αυτό το μπλοκ πρώτο, όλες οι εξαιρέσεις θα συλλαμβάνονταν σε αυτό χωρίς ο κώδικας να φτάσει στα επόμενα μπλοκ `catch`. Επομένως, είναι σημαντικό να έχουμε πιο συγκεκριμένες εξαιρέσεις (δηλαδή αυτές που κληρονομούν από άλλες) πιο ψηλά στη σειρά των μπλοκ `catch` από τις εξαιρέσεις των γονέων τους.
Επαναλήψεις .[#toc-iterations]
------------------------------
Στην PHP, μπορείτε να διατρέχετε αντικείμενα με τη χρήση του βρόχου `foreach`, όπως ακριβώς κάνετε βρόχο σε έναν πίνακα. Για να λειτουργήσει αυτό, το αντικείμενο πρέπει να υλοποιεί μια ειδική διεπαφή.
Η πρώτη επιλογή είναι να υλοποιήσετε τη διεπαφή `Iterator`, η οποία έχει τις μεθόδους `current()` που επιστρέφουν την τρέχουσα τιμή, `key()` που επιστρέφουν το κλειδί, `next()` που μεταβαίνουν στην επόμενη τιμή, `rewind()` που μεταβαίνουν στην αρχή και `valid()` που ελέγχουν αν έχουμε φτάσει στο τέλος.
Η άλλη επιλογή είναι να υλοποιήσουμε μια διεπαφή `IteratorAggregate`, η οποία έχει μόνο μια μέθοδο `getIterator()`. Αυτή είτε επιστρέφει ένα αντικείμενο τοποθέτησης που θα παρέχει τη διάσχιση, είτε μπορεί να είναι μια γεννήτρια, η οποία είναι μια ειδική συνάρτηση που χρησιμοποιεί το `yield` για να επιστρέφει διαδοχικά τα κλειδιά και τις τιμές:
```php
class Person
{
public function __construct(
public int $age,
) {
}
}
class Registry implements IteratorAggregate
{
private array $people = [];
public function addPerson(Person $person): void
{
$this->people[] = $person;
}
public function getIterator(): Generator
{
foreach ($this->people as $person) {
yield $person;
}
}
}
$list = new Registry;
$list->addPerson(new Person(30));
$list->addPerson(new Person(25));
foreach ($list as $person) {
echo "Age: {$person->age} years\n";
}
```
Βέλτιστες πρακτικές .[#toc-best-practices]
------------------------------------------
Μόλις μάθετε τις βασικές αρχές του αντικειμενοστραφούς προγραμματισμού, είναι ζωτικής σημασίας να εστιάσετε στις βέλτιστες πρακτικές του OOP. Αυτές θα σας βοηθήσουν να γράψετε κώδικα που δεν είναι μόνο λειτουργικός αλλά και ευανάγνωστος, κατανοητός και εύκολα συντηρήσιμος.
1) **Διαχωρισμός των ανησυχιών**: Κάθε κλάση θα πρέπει να έχει σαφώς καθορισμένη ευθύνη και να ασχολείται μόνο με μία πρωταρχική εργασία. Αν μια κλάση κάνει πάρα πολλά πράγματα, ίσως είναι σκόπιμο να τη χωρίσετε σε μικρότερες, εξειδικευμένες κλάσεις.
2) **Ενθυλάκωση**: Τα δεδομένα και οι μέθοδοι θα πρέπει να είναι όσο το δυνατόν πιο κρυφά και προσβάσιμα μόνο μέσω μιας καθορισμένης διεπαφής. Αυτό σας επιτρέπει να αλλάξετε την εσωτερική υλοποίηση μιας κλάσης χωρίς να επηρεάζεται ο υπόλοιπος κώδικας.
3) **Έγχυση εξάρτησης**: Αντί να δημιουργείτε εξαρτήσεις απευθείας μέσα σε μια κλάση, θα πρέπει να τις "εγχέετε" από έξω. Για μια βαθύτερη κατανόηση αυτής της αρχής, σας συνιστούμε τα [κεφάλαια για το Dependency Injection |dependency-injection:introduction].