-
-
Notifications
You must be signed in to change notification settings - Fork 277
Expand file tree
/
Copy pathintroduction-to-object-oriented-programming.texy
More file actions
841 lines (609 loc) · 29.9 KB
/
introduction-to-object-oriented-programming.texy
File metadata and controls
841 lines (609 loc) · 29.9 KB
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
Wprowadzenie do programowania obiektowego
*****************************************
.[perex]
Termin "OOP" oznacza programowanie obiektowe, które jest sposobem organizacji i strukturyzacji kodu. OOP pozwala nam postrzegać program jako zbiór obiektów, które komunikują się ze sobą, zamiast sekwencji poleceń i funkcji.
W OOP "obiekt" to jednostka, która zawiera dane i funkcje, które operują na tych danych. Obiekty są tworzone na podstawie "klas", które możemy rozumieć jako plany lub szablony dla obiektów. Kiedy mamy klasę, możemy utworzyć jej "instancję", czyli konkretny obiekt stworzony na podstawie tej klasy.
Pokażmy, jak możemy stworzyć prostą klasę w PHP. Podczas definiowania klasy użyjemy słowa kluczowego "class", następnie nazwy klasy, a potem nawiasów klamrowych, które otaczają funkcje (nazywane "metodami") i zmienne klasy (nazywane "właściwościami" lub po angielsku "property"):
```php
class Samochod
{
function zatrab()
{
echo 'Bip bip!';
}
}
```
W tym przykładzie stworzyliśmy klasę o nazwie `Samochod` z jedną funkcją (lub "metodą") o nazwie `zatrab`.
Każda klasa powinna rozwiązywać tylko jedno główne zadanie. Jeśli klasa robi zbyt wiele rzeczy, może być wskazane podzielenie jej na mniejsze, wyspecjalizowane klasy.
Klasy zazwyczaj przechowujemy w osobnych plikach, aby kod był zorganizowany i łatwo się w nim orientować. Nazwa pliku powinna odpowiadać nazwie klasy, więc dla klasy `Samochod` nazwa pliku byłaby `Samochod.php`.
Podczas nazywania klas dobrze jest trzymać się konwencji "PascalCase", co oznacza, że każde słowo w nazwie zaczyna się wielką literą i nie ma między nimi żadnych podkreśleń ani innych separatorów. Metody i właściwości używają konwencji "camelCase", co oznacza, że zaczynają się małą literą.
Niektóre metody w PHP mają specjalne zadania i są oznaczone prefiksem `__` (dwa podkreślenia). Jedną z najważniejszych specjalnych metod jest "konstruktor", który jest oznaczony jako `__construct`. Konstruktor to metoda, która jest automatycznie wywoływana, gdy tworzysz nową instancję klasy.
Konstruktor często używamy do ustawienia początkowego stanu obiektu. Na przykład, tworząc obiekt reprezentujący osobę, możesz wykorzystać konstruktor do ustawienia jej wieku, imienia lub innych właściwości.
Pokażmy, jak użyć konstruktora w PHP:
```php
class Osoba
{
private $wiek;
function __construct($wiek)
{
$this->wiek = $wiek;
}
function ileMaszLat()
{
return $this->wiek;
}
}
$osoba = new Osoba(25);
echo $osoba->ileMaszLat(); // Wyświetli: 25
```
W tym przykładzie klasa `Osoba` ma właściwość (zmienną) `$wiek` oraz konstruktor, który ustawia tę właściwość. Metoda `ileMaszLat()` następnie umożliwia dostęp do wieku osoby.
Pseudozmienna `$this` jest używana wewnątrz klasy do uzyskania dostępu do właściwości i metod obiektu.
Słowo kluczowe `new` jest używane do tworzenia nowej instancji klasy. W powyższym przykładzie stworzyliśmy nową osobę w wieku 25 lat.
Możesz również ustawić wartości domyślne dla parametrów konstruktora, jeśli nie są one określone podczas tworzenia obiektu. Na przykład:
```php
class Osoba
{
private $wiek;
function __construct($wiek = 20)
{
$this->wiek = $wiek;
}
function ileMaszLat()
{
return $this->wiek;
}
}
$osoba = new Osoba; // jeśli nie przekazujemy żadnego argumentu, nawiasy można pominąć
echo $osoba->ileMaszLat(); // Wyświetli: 20
```
W tym przykładzie, jeśli nie podasz wieku podczas tworzenia obiektu `Osoba`, zostanie użyta wartość domyślna 20.
Przyjemne jest to, że definicję właściwości wraz z jej inicjalizacją przez konstruktor można skrócić i uprościć w ten sposób:
```php
class Osoba
{
function __construct(
private $wiek = 20,
) {
}
}
```
Dla kompletności, oprócz konstruktorów obiekty mogą mieć również destruktory (metoda `__destruct`), które są wywoływane przed zwolnieniem obiektu z pamięci.
Przestrzenie nazw
-----------------
Przestrzenie nazw (lub "namespaces" po angielsku) pozwalają nam organizować i grupować powiązane klasy, funkcje i stałe, jednocześnie unikając konfliktów nazw. Możesz je sobie wyobrazić jako foldery na komputerze, gdzie każdy folder zawiera pliki należące do określonego projektu lub tematu.
Przestrzenie nazw są szczególnie przydatne w większych projektach lub gdy używasz bibliotek stron trzecich, gdzie mogą wystąpić konflikty nazw klas.
Wyobraź sobie, że masz klasę o nazwie `Samochod` w swoim projekcie i chcesz ją umieścić w przestrzeni nazw o nazwie `Transport`. Zrobisz to w ten sposób:
```php
namespace Transport;
class Samochod
{
function zatrab()
{
echo 'Bip bip!';
}
}
```
Jeśli chcesz użyć klasy `Samochod` w innym pliku, musisz określić, z jakiej przestrzeni nazw pochodzi klasa:
```php
$auto = new Transport\Samochod;
```
Dla uproszczenia możesz na początku pliku określić, której klasy z danej przestrzeni nazw chcesz używać, co pozwala tworzyć instancje bez konieczności podawania całej ścieżki:
```php
use Transport\Samochod;
$auto = new Samochod;
```
Dziedziczenie
-------------
Dziedziczenie jest narzędziem programowania obiektowego, które pozwala tworzyć nowe klasy na podstawie już istniejących klas, przejmować ich właściwości i metody oraz rozszerzać lub przedefiniowywać je według potrzeb. Dziedziczenie pozwala zapewnić ponowne wykorzystanie kodu i hierarchię klas.
Upraszczając, jeśli mamy jedną klasę i chcielibyśmy stworzyć inną, pochodną od niej, ale z kilkoma zmianami, możemy nową klasę "odziedziczyć" z pierwotnej klasy.
W PHP dziedziczenie realizujemy za pomocą słowa kluczowego `extends`.
Nasza klasa `Osoba` przechowuje informację o wieku. Możemy mieć inną klasę `Student`, która rozszerza `Osobę` i dodaje informację o kierunku studiów.
Spójrzmy na przykład:
```php
class Osoba
{
private $wiek;
function __construct($wiek)
{
$this->wiek = $wiek;
}
function wypiszInformacje()
{
echo "Wiek: {$this->wiek} lat\n";
}
}
class Student extends Osoba
{
private $kierunek;
function __construct($wiek, $kierunek)
{
parent::__construct($wiek);
$this->kierunek = $kierunek;
}
function wypiszInformacje()
{
parent::wypiszInformacje();
echo "Kierunek studiów: {$this->kierunek} \n";
}
}
$student = new Student(20, 'Informatyka');
$student->wypiszInformacje();
```
Jak działa ten kod?
- Użyliśmy słowa kluczowego `extends` do rozszerzenia klasy `Osoba`, co oznacza, że klasa `Student` odziedziczy wszystkie metody i właściwości z `Osoby`.
- Słowo kluczowe `parent::` pozwala nam wywoływać metody z klasy nadrzędnej. W tym przypadku wywołaliśmy konstruktor z klasy `Osoba` przed dodaniem własnej funkcjonalności do klasy `Student`. Podobnie wywołaliśmy metodę `wypiszInformacje()` przodka przed wypisaniem informacji o studencie.
Dziedziczenie jest przeznaczone dla sytuacji, gdy istnieje relacja "jest" między klasami. Na przykład `Student` jest `Osobą`. Kot jest zwierzęciem. Daje nam to możliwość w przypadkach, gdy w kodzie oczekujemy jednego obiektu (np. "Osoba"), użycia zamiast niego obiektu dziedziczonego (np. "Student").
Ważne jest, aby zdać sobie sprawę, że głównym celem dziedziczenia **nie jest** zapobieganie duplikacji kodu. Wręcz przeciwnie, niewłaściwe wykorzystanie dziedziczenia może prowadzić do skomplikowanego i trudnego do utrzymania kodu. Jeśli relacja "jest" między klasami nie istnieje, powinniśmy zamiast dziedziczenia rozważyć kompozycję.
Zauważ, że metody `wypiszInformacje()` w klasach `Osoba` i `Student` wypisują nieco inne informacje. Możemy dodać kolejne klasy (na przykład `Pracownik`), które będą dostarczać kolejne implementacje tej metody. Zdolność obiektów różnych klas do reagowania na tę samą metodę na różne sposoby nazywa się polimorfizmem:
```php
$osoby = [
new Osoba(30),
new Student(20, 'Informatyka'),
new Pracownik(45, 'Dyrektor'),
];
foreach ($osoby as $osoba) {
$osoba->wypiszInformacje();
}
```
Kompozycja
----------
Kompozycja to technika, w której zamiast dziedziczyć właściwości i metody innej klasy, po prostu wykorzystujemy jej instancję w naszej klasie. Pozwala to łączyć funkcjonalności i właściwości wielu klas bez konieczności tworzenia złożonych struktur dziedziczenia.
Spójrzmy na przykład. Mamy klasę `Silnik` i klasę `Samochod`. Zamiast mówić "Samochód jest Silnikiem", mówimy "Samochód ma Silnik", co jest typową relacją kompozycji.
```php
class Silnik
{
function wlacz()
{
echo 'Silnik pracuje.';
}
}
class Samochod
{
private $silnik;
function __construct()
{
$this->silnik = new Silnik;
}
function start()
{
$this->silnik->wlacz();
echo 'Samochód jest gotowy do jazdy!';
}
}
$samochod = new Samochod;
$samochod->start();
```
Tutaj `Samochod` nie ma wszystkich właściwości i metod `Silnika`, ale ma do niego dostęp za pośrednictwem właściwości `$silnik`.
Zaletą kompozycji jest większa elastyczność w projektowaniu i lepsza możliwość modyfikacji w przyszłości.
Widoczność
----------
W PHP możesz zdefiniować "widoczność" dla właściwości, metod i stałych klasy. Widoczność określa, skąd możesz uzyskać dostęp do tych elementów.
1. **Public:** Jeśli element jest oznaczony jako `public`, oznacza to, że możesz uzyskać do niego dostęp z dowolnego miejsca, nawet spoza klasy.
2. **Protected:** Element oznaczony jako `protected` jest dostępny tylko w ramach danej klasy i wszystkich jej potomków (klas, które dziedziczą z tej klasy).
3. **Private:** Jeśli element jest `private`, możesz uzyskać do niego dostęp tylko z wnętrza klasy, w której został zdefiniowany.
Jeśli nie określisz widoczności, PHP automatycznie ustawi ją na `public`.
Spójrzmy na przykładowy kod:
```php
class PrzykladWidocznosci
{
public $wlasciwoscPubliczna = 'Publiczna';
protected $wlasciwoscChroniona = 'Chroniona';
private $wlasciwoscPrywatna = 'Prywatna';
public function wypiszWlasciwosci()
{
echo $this->wlasciwoscPubliczna; // Działa
echo $this->wlasciwoscChroniona; // Działa
echo $this->wlasciwoscPrywatna; // Działa
}
}
$obiekt = new PrzykladWidocznosci;
$obiekt->wypiszWlasciwosci();
echo $obiekt->wlasciwoscPubliczna; // Działa
// echo $obiekt->wlasciwoscChroniona; // Zgłosi błąd
// echo $obiekt->wlasciwoscPrywatna; // Zgłosi błąd
```
Kontynuujemy z dziedziczeniem klasy:
```php
class PotomekKlasy extends PrzykladWidocznosci
{
public function wypiszWlasciwosci()
{
echo $this->wlasciwoscPubliczna; // Działa
echo $this->wlasciwoscChroniona; // Działa
// echo $this->wlasciwoscPrywatna; // Zgłosi błąd
}
}
```
W tym przypadku metoda `wypiszWlasciwosci()` w klasie `PotomekKlasy` może uzyskać dostęp do publicznych i chronionych właściwości, ale nie może uzyskać dostępu do prywatnych właściwości klasy rodzicielskiej.
Dane i metody powinny być jak najbardziej ukryte i dostępne tylko za pośrednictwem zdefiniowanego interfejsu. Pozwoli to na zmianę wewnętrznej implementacji klasy bez wpływu na resztę kodu.
Słowo kluczowe `final`
----------------------
W PHP możemy użyć słowa kluczowego `final`, jeśli chcemy uniemożliwić dziedziczenie lub nadpisywanie klasy, metody lub stałej. Kiedy oznaczymy klasę jako `final`, nie może być ona rozszerzana. Kiedy oznaczymy metodę jako `final`, nie może być ona nadpisana w klasie potomnej.
Świadomość, że dana klasa lub metoda nie będzie dalej modyfikowana, pozwala nam łatwiej wprowadzać zmiany, nie martwiąc się o możliwe konflikty. Na przykład możemy dodać nową metodę bez obaw, że któryś z jej potomków ma już metodę o tej samej nazwie i doszłoby do kolizji. Lub możemy zmienić parametry metody, ponieważ znów nie ma ryzyka, że spowodujemy niezgodność z nadpisaną metodą w potomku.
```php
final class KlasaFinalna
{
}
// Poniższy kod spowoduje błąd, ponieważ nie możemy dziedziczyć z klasy finalnej.
class PotomekKlasyFinalnej extends KlasaFinalna
{
}
```
W tym przykładzie próba dziedziczenia z finalnej klasy `KlasaFinalna` spowoduje błąd.
Statyczne właściwości i metody
------------------------------
Kiedy w PHP mówimy o "statycznych" elementach klasy, mamy na myśli metody i właściwości, które należą do samej klasy, a nie do konkretnej instancji tej klasy. Oznacza to, że nie musisz tworzyć instancji klasy, aby mieć do nich dostęp. Zamiast tego wywołujesz je lub uzyskujesz do nich dostęp bezpośrednio przez nazwę klasy.
Pamiętaj, że ponieważ elementy statyczne należą do klasy, a nie do jej instancji, nie możesz używać pseudozmiennej `$this` wewnątrz metod statycznych.
Używanie właściwości statycznych prowadzi do [nieprzejrzystego kodu pełnego pułapek|dependency-injection:global-state], dlatego nigdy nie powinieneś ich używać i nawet nie będziemy tutaj pokazywać przykładu użycia. Natomiast metody statyczne są przydatne. Przykład użycia:
```php
class Kalkulator
{
public static function dodawanie($a, $b)
{
return $a + $b;
}
public static function odejmowanie($a, $b)
{
return $a - $b;
}
}
// Użycie metody statycznej bez tworzenia instancji klasy
echo Kalkulator::dodawanie(5, 3); // Wynik: 8
echo Kalkulator::odejmowanie(5, 3); // Wynik: 2
```
W tym przykładzie stworzyliśmy klasę `Kalkulator` z dwiema metodami statycznymi. Możemy wywoływać te metody bezpośrednio bez tworzenia instancji klasy za pomocą operatora `::`. Metody statyczne są szczególnie przydatne do operacji, które nie zależą od stanu konkretnej instancji klasy.
Stałe klasowe
-------------
W ramach klas mamy możliwość definiowania stałych. Stałe to wartości, które nigdy się nie zmienią podczas działania programu. W przeciwieństwie do zmiennych, wartość stałej pozostaje zawsze taka sama.
```php
class Samochod
{
public const LiczbaKol = 4;
public function wyswietlLiczbeKol(): int
{
echo self::LiczbaKol;
}
}
echo Samochod::LiczbaKol; // Wyjście: 4
```
W tym przykładzie mamy klasę `Samochod` ze stałą `LiczbaKol`. Kiedy chcemy uzyskać dostęp do stałej wewnątrz klasy, możemy użyć słowa kluczowego `self` zamiast nazwy klasy.
Interfejsy obiektowe
--------------------
Interfejsy obiektowe działają jak "kontrakty" dla klas. Jeśli klasa ma implementować interfejs obiektowy, musi zawierać wszystkie metody, które ten interfejs definiuje. Jest to świetny sposób na zapewnienie, że określone klasy przestrzegają tej samej "umowy" lub struktury.
W PHP interfejs definiuje się słowem kluczowym `interface`. Wszystkie metody zdefiniowane w interfejsie są publiczne (`public`). Kiedy klasa implementuje interfejs, używa słowa kluczowego `implements`.
```php
interface Zwierze
{
function wydajDzwiek();
}
class Kot implements Zwierze
{
public function wydajDzwiek()
{
echo 'Miau';
}
}
$kot = new Kot;
$kot->wydajDzwiek();
```
Jeśli klasa implementuje interfejs, ale nie są w niej zdefiniowane wszystkie oczekiwane metody, PHP zgłosi błąd.
Klasa może implementować wiele interfejsów jednocześnie, co stanowi różnicę w porównaniu do dziedziczenia, gdzie klasa może dziedziczyć tylko z jednej klasy:
```php
interface Stroz
{
function pilnujDomu();
}
class Pies implements Zwierze, Stroz
{
public function wydajDzwiek()
{
echo 'Hau';
}
public function pilnujDomu()
{
echo 'Pies uważnie pilnuje domu';
}
}
```
Klasy abstrakcyjne
------------------
Klasy abstrakcyjne służą jako podstawowe szablony dla innych klas, ale nie można tworzyć ich instancji bezpośrednio. Zawierają kombinację kompletnych metod i metod abstrakcyjnych, które nie mają zdefiniowanej zawartości. Klasy, które dziedziczą z klas abstrakcyjnych, muszą dostarczyć definicje dla wszystkich metod abstrakcyjnych z przodka.
Do definiowania klasy abstrakcyjnej używamy słowa kluczowego `abstract`.
```php
abstract class KlasaAbstrakcyjna
{
public function zwyklaMetoda()
{
echo 'To jest zwykła metoda';
}
abstract public function metodaAbstrakcyjna();
}
class Potomek extends KlasaAbstrakcyjna
{
public function metodaAbstrakcyjna()
{
echo 'To jest implementacja metody abstrakcyjnej';
}
}
$instancja = new Potomek;
$instancja->zwyklaMetoda();
$instancja->metodaAbstrakcyjna();
```
W tym przykładzie mamy klasę abstrakcyjną z jedną zwykłą i jedną abstrakcyjną metodą. Następnie mamy klasę `Potomek`, która dziedziczy z `KlasaAbstrakcyjna` i dostarcza implementację dla metody abstrakcyjnej.
Jak właściwie różnią się interfejsy i klasy abstrakcyjne? Klasy abstrakcyjne mogą zawierać zarówno abstrakcyjne, jak i konkretne metody, podczas gdy interfejsy jedynie definiują, jakie metody musi implementować klasa, ale nie dostarczają żadnej implementacji. Klasa może dziedziczyć tylko z jednej klasy abstrakcyjnej, ale może implementować dowolną liczbę interfejsów.
Kontrola typów
--------------
W programowaniu bardzo ważne jest, aby mieć pewność, że dane, z którymi pracujemy, są odpowiedniego typu. W PHP mamy narzędzia, które nam to zapewniają. Weryfikacja, czy dane mają poprawny typ, nazywa się "kontrolą typów".
Typy, na które możemy natknąć się w PHP:
1. **Typy podstawowe**: Obejmują `int` (liczby całkowite), `float` (liczby dziesiętne), `bool` (wartości logiczne), `string` (ciągi znaków), `array` (tablice) i `null`.
2. **Klasy**: Jeśli chcemy, aby wartość była instancją określonej klasy.
3. **Interfejsy**: Definiuje zestaw metod, które klasa musi implementować. Wartość, która spełnia interfejs, musi mieć te metody.
4. **Typy mieszane**: Możemy określić, że zmienna może mieć więcej niż jeden dozwolony typ.
5. **Void**: Ten specjalny typ oznacza, że funkcja lub metoda nie zwraca żadnej wartości.
Pokażmy, jak zmodyfikować kod, aby zawierał typy:
```php
class Osoba
{
private int $wiek;
public function __construct(int $wiek)
{
$this->wiek = $wiek;
}
public function wypiszWiek(): void
{
echo "Ta osoba ma {$this->wiek} lat.";
}
}
/**
* Funkcja, która przyjmuje obiekt klasy Osoba i wyświetla wiek osoby.
*/
function wypiszWiekOsoby(Osoba $osoba): void
{
$osoba->wypiszWiek();
}
```
W ten sposób zapewniliśmy, że nasz kod oczekuje i pracuje z danymi odpowiedniego typu, co pomaga nam zapobiegać potencjalnym błędom.
Niektórych typów nie można bezpośrednio zapisać w PHP. W takim przypadku podaje się je w komentarzu phpDoc, który jest standardowym formatem dokumentacji kodu PHP zaczynającym się od `/**` i kończącym `*/`. Umożliwia dodawanie opisów klas, metod itp. A także podawanie złożonych typów za pomocą tzw. adnotacji `@var`, `@param` i `@return`. Te typy są następnie wykorzystywane przez narzędzia do statycznej analizy kodu, ale samo PHP ich nie kontroluje.
```php
class Lista
{
/** @var array<Osoba> zapis mówi, że jest to tablica obiektów Osoba */
private array $osoby = [];
public function dodajOsobe(Osoba $osoba): void
{
$this->osoby[] = $osoba;
}
}
```
Porównywanie i tożsamość
------------------------
W PHP możesz porównywać obiekty na dwa sposoby:
1. Porównanie wartości `==`: Sprawdza, czy obiekty są tej samej klasy i mają te same wartości w swoich właściwościach.
2. Tożsamość `===`: Sprawdza, czy chodzi o tę samą instancję obiektu.
```php
class Samochod
{
public string $marka;
public function __construct(string $marka)
{
$this->marka = $marka;
}
}
$samochod1 = new Samochod('Skoda');
$samochod2 = new Samochod('Skoda');
$samochod3 = $samochod1;
var_dump($samochod1 == $samochod2); // true, ponieważ mają tę samą wartość
var_dump($samochod1 === $samochod2); // false, ponieważ nie są tą samą instancją
var_dump($samochod1 === $samochod3); // true, ponieważ $samochod3 jest tą samą instancją co $samochod1
```
Operator `instanceof`
---------------------
Operator `instanceof` pozwala sprawdzić, czy dany obiekt jest instancją określonej klasy, potomkiem tej klasy, lub czy implementuje określony interfejs.
Wyobraźmy sobie, że mamy klasę `Osoba` i inną klasę `Student`, która jest potomkiem klasy `Osoba`:
```php
class Osoba
{
private int $wiek;
public function __construct(int $wiek)
{
$this->wiek = $wiek;
}
}
class Student extends Osoba
{
private string $kierunek;
public function __construct(int $wiek, string $kierunek)
{
parent::__construct($wiek);
$this->kierunek = $kierunek;
}
}
$student = new Student(20, 'Informatyka');
// Sprawdzenie, czy $student jest instancją klasy Student
var_dump($student instanceof Student); // Wyjście: bool(true)
// Sprawdzenie, czy $student jest instancją klasy Osoba (ponieważ Student jest potomkiem Osoba)
var_dump($student instanceof Osoba); // Wyjście: bool(true)
```
Z wyników widać, że obiekt `$student` jest jednocześnie uważany za instancję obu klas - `Student` i `Osoba`.
Fluent Interfaces
-----------------
"Płynny interfejs" (ang. "Fluent Interface") to technika w OOP, która pozwala łączyć metody w łańcuch w jednym wywołaniu. To często upraszcza i uczytelnia kod.
Kluczowym elementem płynnego interfejsu jest to, że każda metoda w łańcuchu zwraca odwołanie do bieżącego obiektu. Osiągamy to, używając `return $this;` na końcu metody. Ten styl programowania jest często kojarzony z metodami zwanymi "setterami", które ustawiają wartości właściwości obiektu.
Pokażemy, jak może wyglądać płynny interfejs na przykładzie wysyłania e-maili:
```php
public function wyslijWiadomosc()
{
$email = new Email;
$email->setFrom('[email protected]')
->setRecipient('[email protected]')
->setMessage('Hello, this is a message.')
->send();
}
```
W tym przykładzie metody `setFrom()`, `setRecipient()` i `setMessage()` służą do ustawienia odpowiednich wartości (nadawcy, odbiorcy, treści wiadomości). Po ustawieniu każdej z tych wartości metody zwracają nam bieżący obiekt (`$email`), co pozwala nam połączyć kolejną metodę za nią. Na końcu wywołujemy metodę `send()`, która faktycznie wysyła e-mail.
Dzięki płynnym interfejsom możemy pisać kod, który jest intuicyjny i łatwy do odczytania.
Kopiowanie za pomocą `clone`
----------------------------
W PHP możemy utworzyć kopię obiektu za pomocą operatora `clone`. W ten sposób otrzymujemy nową instancję o identycznej zawartości.
Jeśli podczas kopiowania obiektu potrzebujemy zmodyfikować niektóre jego właściwości, możemy zdefiniować w klasie specjalną metodę `__clone()`. Ta metoda jest automatycznie wywoływana, gdy obiekt jest klonowany.
```php
class Owca
{
public string $imie;
public function __construct(string $imie)
{
$this->imie = $imie;
}
public function __clone()
{
$this->imie = 'Klon ' . $this->imie;
}
}
$oryginal = new Owca('Dolly');
echo $oryginal->imie . "\n"; // Wyświetli: Dolly
$klon = clone $oryginal;
echo $klon->imie . "\n"; // Wyświetli: Klon Dolly
```
W tym przykładzie mamy klasę `Owca` z jedną właściwością `$imie`. Kiedy klonujemy instancję tej klasy, metoda `__clone()` dba o to, aby nazwa sklonowanej owcy otrzymała prefiks "Klon".
Traity
------
Traity w PHP to narzędzie, które pozwala współdzielić metody, właściwości i stałe między klasami oraz zapobiegać duplikacji kodu. Możesz je sobie wyobrazić jako mechanizm "kopiuj i wklej" (Ctrl-C i Ctrl-V), gdzie zawartość trait jest "wklejana" do klas. Pozwala to na ponowne wykorzystanie kodu bez konieczności tworzenia skomplikowanych hierarchii klas.
Pokażmy prosty przykład, jak używać traitów w PHP:
```php
trait Trabienie
{
public function zatrab()
{
echo 'Bip bip!';
}
}
class Samochod
{
use Trabienie;
}
class Ciezarowka
{
use Trabienie;
}
$samochod = new Samochod;
$samochod->zatrab(); // Wyświetli 'Bip bip!'
$ciezarowka = new Ciezarowka;
$ciezarowka->zatrab(); // Również wyświetli 'Bip bip!'
```
W tym przykładzie mamy trait o nazwie `Trabienie`, który zawiera jedną metodę `zatrab()`. Następnie mamy dwie klasy: `Samochod` i `Ciezarowka`, które obie używają traitu `Trabienie`. Dzięki temu obie klasy "mają" metodę `zatrab()`, i możemy ją wywoływać na obiektach obu klas.
Traity pozwalają łatwo i efektywnie współdzielić kod między klasami. Jednocześnie nie wchodzą one w hierarchię dziedziczenia, tj. `$samochod instanceof Trabienie` zwróci `false`.
Wyjątki
-------
Wyjątki w OOP pozwalają nam elegancko obsługiwać błędy i nieoczekiwane sytuacje w naszym kodzie. Są to obiekty, które niosą informacje o błędzie lub nietypowej sytuacji.
W PHP mamy wbudowaną klasę `Exception`, która służy jako podstawa dla wszystkich wyjątków. Ma ona kilka metod, które pozwalają nam uzyskać więcej informacji o wyjątku, takich jak komunikat o błędzie, plik i linia, w której wystąpił błąd, itp.
Kiedy w kodzie wystąpi błąd, możemy "rzucić" wyjątek za pomocą słowa kluczowego `throw`.
```php
function dzielenie(float $a, float $b): float
{
if ($b === 0.0) { // Porównanie z float
throw new Exception('Dzielenie przez zero!');
}
return $a / $b;
}
```
Kiedy funkcja `dzielenie()` otrzyma jako drugi argument zero, rzuci wyjątek z komunikatem o błędzie `'Dzielenie przez zero!'`. Aby zapobiec awarii programu po rzuceniu wyjątku, przechwytujemy go w bloku `try/catch`:
```php
try {
echo dzielenie(10, 0);
} catch (Exception $e) {
echo 'Wyjątek przechwycony: '. $e->getMessage();
}
```
Kod, który może rzucić wyjątek, jest opakowany w blok `try`. Jeśli wyjątek zostanie rzucony, wykonanie kodu przenosi się do bloku `catch`, gdzie możemy przetworzyć wyjątek (np. wypisać komunikat o błędzie).
Po blokach `try` i `catch` możemy dodać opcjonalny blok `finally`, który wykona się zawsze, niezależnie od tego, czy wyjątek został rzucony, czy nie (nawet w przypadku, gdy w bloku `try` lub `catch` użyjemy instrukcji `return`, `break` lub `continue`):
```php
try {
echo dzielenie(10, 0);
} catch (Exception $e) {
echo 'Wyjątek przechwycony: '. $e->getMessage();
} finally {
// Kod, który wykona się zawsze, niezależnie od tego, czy wyjątek został rzucony, czy nie
}
```
Możemy również tworzyć własne klasy (hierarchię) wyjątków, które dziedziczą z klasy Exception. Jako przykład wyobraźmy sobie prostą aplikację bankową, która pozwala dokonywać wpłat i wypłat:
```php
class WyjatekBankowy extends Exception {}
class WyjatekNiewystarczajacychSrodkow extends WyjatekBankowy {}
class WyjatekPrzekroczeniaLimitu extends WyjatekBankowy {}
class KontoBankowe
{
private int $saldo = 0;
private int $limitDzienny = 1000;
public function wplac(int $kwota): int
{
$this->saldo += $kwota;
return $this->saldo;
}
public function wyplac(int $kwota): int
{
if ($kwota > $this->saldo) {
throw new WyjatekNiewystarczajacychSrodkow('Na koncie nie ma wystarczających środków.');
}
if ($kwota > $this->limitDzienny) {
throw new WyjatekPrzekroczeniaLimitu('Przekroczono dzienny limit wypłat.');
}
$this->saldo -= $kwota;
return $this->saldo;
}
}
```
Dla jednego bloku `try` można podać wiele bloków `catch`, jeśli oczekujesz różnych typów wyjątków.
```php
$konto = new KontoBankowe;
$konto->wplac(500);
try {
$konto->wyplac(1500);
} catch (WyjatekPrzekroczeniaLimitu $e) {
echo $e->getMessage();
} catch (WyjatekNiewystarczajacychSrodkow $e) {
echo $e->getMessage();
} catch (WyjatekBankowy $e) {
echo 'Wystąpił błąd podczas wykonywania operacji.';
}
```
W tym przykładzie ważne jest zwrócenie uwagi na kolejność bloków `catch`. Ponieważ wszystkie wyjątki dziedziczą z `WyjatekBankowy`, gdybyśmy mieli ten blok jako pierwszy, przechwyciłby on wszystkie wyjątki, uniemożliwiając kodowi dotarcie do kolejnych bloków `catch`. Dlatego ważne jest, aby bardziej specyficzne wyjątki (tj. te, które dziedziczą z innych) znajdowały się w bloku `catch` wyżej w kolejności niż ich wyjątki nadrzędne.
Iteracja
--------
W PHP możesz przechodzić przez obiekty za pomocą pętli `foreach`, podobnie jak przechodzisz przez tablice. Aby to działało, obiekt musi implementować specjalny interfejs.
Pierwszą opcją jest implementacja interfejsu `Iterator`, który ma metody `current()` zwracającą bieżącą wartość, `key()` zwracającą klucz, `next()` przechodzącą do następnej wartości, `rewind()` przechodzącą na początek i `valid()` sprawdzającą, czy jeszcze nie jesteśmy na końcu.
Drugą opcją jest implementacja interfejsu `IteratorAggregate`, który ma tylko jedną metodę `getIterator()`. Zwraca ona albo obiekt zastępczy, który będzie zapewniał iterację, albo może reprezentować generator, czyli specjalną funkcję, w której używa się `yield` do stopniowego zwracania kluczy i wartości:
```php
class Osoba
{
public function __construct(
public int $wiek,
) {
}
}
class Lista implements IteratorAggregate
{
private array $osoby = [];
public function dodajOsobe(Osoba $osoba): void
{
$this->osoby[] = $osoba;
}
public function getIterator(): Generator
{
foreach ($this->osoby as $osoba) {
yield $osoba;
}
}
}
$lista = new Lista;
$lista->dodajOsobe(new Osoba(30));
$lista->dodajOsobe(new Osoba(25));
foreach ($lista as $osoba) {
echo "Wiek: {$osoba->wiek} lat \n";
}
```
Dobre praktyki
--------------
Kiedy masz już za sobą podstawowe zasady programowania obiektowego, ważne jest, aby skupić się na dobrych praktykach w OOP. Pomogą ci one pisać kod, który jest nie tylko funkcjonalny, ale także czytelny, zrozumiały i łatwy do utrzymania.
1) **Podział odpowiedzialności (Separation of Concerns)**: Każda klasa powinna mieć jasno zdefiniowaną odpowiedzialność i powinna rozwiązywać tylko jedno główne zadanie. Jeśli klasa robi zbyt wiele rzeczy, może być wskazane podzielenie jej na mniejsze, wyspecjalizowane klasy.
2) **Hermetyzacja (Encapsulation)**: Dane i metody powinny być jak najbardziej ukryte i dostępne tylko za pośrednictwem zdefiniowanego interfejsu. Pozwoli to na zmianę wewnętrznej implementacji klasy bez wpływu na resztę kodu.
3) **Wstrzykiwanie zależności (Dependency Injection)**: Zamiast tworzyć zależności bezpośrednio w klasie, powinieneś je "wstrzykiwać" z zewnątrz. Aby lepiej zrozumieć tę zasadę, polecamy [rozdziały o wstrzykiwaniu zależności|dependency-injection:introduction].