OOP Beginnershandleiding (PHP5)
- Inleiding
- Object geörienteerd denken
- Foute denkwijze
- Object georiënteerd programmeren
- Visibility
- Naamgeving
- Constructor __construct()
- Voorbeeld: HTML tabel
- Inheritance
- Voorbeeld: HTML tabel 2 (inheritance)
- Static methods en properties
- Abstract classes en Interfaces
- Magic methods
- Slotwoord en referenties
- Reacties op deze tutorial
Abstract classes en Interfaces
In dit hoofdstuk bekijken we een ander krachtig onderdeel van OOP, namelijk het gebruik van abstract classes en interfaces. Beide middelen zijn bedoeld om de programmeur (jijzelf, of iemand anders) te dwingen bepaalde methods of properties te gebruiken. Op die manier kun je vooraf bepalen hoe bepaalde classes gebruikt dienen te worden of in een applicatie opgenomen dienen te worden.Abstract classes
Een abstract class is een class met of zonder eigen properties en een aantal methods die gedeeltelijk de functionaliteit van de class bepalen maar tegelijkertijd een deel van de functionliteit onbepaald laat. Het onbepaalde gedeelte zijn de abstract methods en deze dienen uitgewerkt te worden in de child class die deze abstract class extend.
Deze lastige definitie is eigenlijk alleen maar goed uit te leggen met een voorbeeld, dus laten we de User class er weer eens bijpakken. Stel je nu de situatie voor dat je een webshop aan het bouwen bent waarbij je twee verschillende typen gebruikers kent: klanten en werknemers. In het hoofdstuk over inheritance hebben we gezien hoe de User class te extenden is tot een Customer class, maar het grote nadeel is dat er in dat geval nog steeds een User object aangemaakt kan worden waar je eigenlijk niets mee kan, we hebben immers alleen klanten en werknemers geen gebruikers zonder functie. Om dat te voorkomen definiëren we de User class nu als abstract, hetgeen ondermeer betekent dat hij niet geïnstantieerd kan worden.
Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
abstract class User {
private $_username;
public function __construct($name) {
$this->_username = $name;
}
public function getUsername() {
return $this->_username;
}
public abstract function getUserStatus();
}
?>
abstract class User {
private $_username;
public function __construct($name) {
$this->_username = $name;
}
public function getUsername() {
return $this->_username;
}
public abstract function getUserStatus();
}
?>
Deze class komt ons inmiddels bekend voor, maar de abstract method getUserStatus() is nieuw. Dat deze method als abstract gedeclareerd is, betekent dat het de verantwoordelijkheid is van de child class om voor de functionaliteit van getUserStatus() te zorgen. Deze method moet wel abstract zijn omdat de functionaliteit verschillend is bij de child classes.
De twee classes die we nu nog missen, Customer en Employee, zijn beide een child van de User class. Beide classes worden dus gedwongen om minimaal de getUserStatus() method te definiëren.
Code
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
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
<?php
class Customer extends User {
private $_customerId;
public function __construct($username, $id) {
$this->_username = $username;
$this->_customerId = $id;
}
public function getUserStatus() {
return 'customer';
}
}
class Employee extends User {
private $_employeeId;
public function __construct($username, $id) {
$this->_username = $username;
$this->_employeeId = $id;
}
public function getUserStatus() {
return 'employee';
}
}
?>
class Customer extends User {
private $_customerId;
public function __construct($username, $id) {
$this->_username = $username;
$this->_customerId = $id;
}
public function getUserStatus() {
return 'customer';
}
}
class Employee extends User {
private $_employeeId;
public function __construct($username, $id) {
$this->_username = $username;
$this->_employeeId = $id;
}
public function getUserStatus() {
return 'employee';
}
}
?>
De twee classes lijken (nog) erg veel op elkaar, het belangrijke verschil zit hem echter in de getUserStatus() method. Als we het geheel samenvoegen met de User class en nog een paar regels procedurele code toevoegen, is dit het resultaat:
Code
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
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
<?php
abstract class User {
private $_username;
public function __construct($name) {
$this->_username = $name;
}
public function getUsername() {
return $this->_username;
}
public abstract function getUserStatus();
}
class Customer extends User {
private $_customerId;
public function __construct($username, $id) {
$this->_username = $username;
$this->_customerId = $id;
}
public function getUserStatus() {
return 'customer';
}
}
class Employee extends User {
private $_employeeId;
public function __construct($username, $id) {
$this->_username = $username;
$this->_employeeId = $id;
}
public function getUserStatus() {
return 'employee';
}
}
$jan = new Customer('jan', 1);
$inge = new Employee('inge', 1);
echo 'Jan is een '.$jan->getUserStatus().'. <br />';
echo 'Inge is een '.$inge->getUserStatus().'.';
?>
abstract class User {
private $_username;
public function __construct($name) {
$this->_username = $name;
}
public function getUsername() {
return $this->_username;
}
public abstract function getUserStatus();
}
class Customer extends User {
private $_customerId;
public function __construct($username, $id) {
$this->_username = $username;
$this->_customerId = $id;
}
public function getUserStatus() {
return 'customer';
}
}
class Employee extends User {
private $_employeeId;
public function __construct($username, $id) {
$this->_username = $username;
$this->_employeeId = $id;
}
public function getUserStatus() {
return 'employee';
}
}
$jan = new Customer('jan', 1);
$inge = new Employee('inge', 1);
echo 'Jan is een '.$jan->getUserStatus().'. <br />';
echo 'Inge is een '.$inge->getUserStatus().'.';
?>
Code: output
1
2
2
Jan is een customer.
Inge is een employee.
Inge is een employee.
Zoals aan de output te zien is, doet de getUserMethod() wat van hem gevraagd wordt.
Dit voorbeeld geeft zeer eenvoudig de werking van abstract classes weer. Op deze manier kun je vooraf een gemeenschappelijk gedeelte van meerdere classes programmeren om deze abstract class later te extenden met de classes die je daadwerkelijk gaat gebruiken. Het grote voordeel: de gemeenschappelijke functionaliteit hoef je maar een keer te programmeren.
Interfaces
Een interface is een overeenkomst tussen ongerelateerde objecten voor het uitvoeren van dezelfde functionaliteit. Een interface stelt je in staat om aan te geven dat een object een bepaalde functionaliteit moet bezitten, maar het bepaalt niet hoe het object dat moet doen. De child class is dus vrij om de hele implementatie te doen, zolang hij maar voldoet aan de functionaliteit die de interface afdwingt.
In het geval van een interface extend de child class de parent niet, maar implementeert hij hem. Daartoe maken we gebruik van het keyword 'implements'.
Stel dat we bezig zijn met het ontwikkelen van een applicatie die de communicatie met verschillende type databases moet kunnen afhandelen. Bekend is dat de ene database anders werkt dan de ander en dat vaak verschillende (PHP) functies nodig zijn. Het is onmogelijk om één class te schrijven die met alle type databases werkt, sterker nog voor elke database heb je een aparte class nodig. Maar we kunnen wel vooraf de functionaliteit bepalen die elke database class minimaal moet hebben, ongeacht de database waarmee we werken. Dat zou er als volgt uit kunnen zien:
Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
interface Database
{
public function connect();
public function error();
public function errno();
public function escape($string);
public function query($query);
public function fetchArray($result);
public function fetchRow($result);
public function fetchAssoc($result);
public function fetchObject($result);
public function numRows($result);
public function close();
}
?>
interface Database
{
public function connect();
public function error();
public function errno();
public function escape($string);
public function query($query);
public function fetchArray($result);
public function fetchRow($result);
public function fetchAssoc($result);
public function fetchObject($result);
public function numRows($result);
public function close();
}
?>
Deze interface dwingt elke class die hem implementeert om minimaal functionaliteit toe te kennen aan deze methods. Bovendien moet de child class bij elke method minimaal de parameters accepteren die in de interface bepaald zijn. Een method mag meer parameters hebben, zolang ze optioneel zijn, maar zeker niet minder.
Een child class die de communicatie met een MySQL database kan afhandelen, zou er als volgt uit kunnen zien:
Code
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
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
<?php
class Mysql_Database implements Database {
private $_link;
public function connect($server='', $username='', $password='', $new_link=true, $client_flags=0) {
$this->_link = mysql_connect($server, $username, $password, $new_link, $client_flags);
}
public function error() {
return mysql_errno($this->_link);
}
public function errno() {
return mysql_error($this->_link);
}
public function escape($string) {
return mysql_real_escape_string($string, $this->_link);
}
public function query($query) {
return mysql_query($query, $this->_link);
}
public function fetchArray($result, $array_type = MYSQL_BOTH) {
return mysql_fetch_array($result, $array_type);
}
public function fetchRow($result) {
return mysql_fetch_row($result);
}
public function fetchAssoc($result) {
return mysql_fetch_assoc($result);
}
public function fetchObject($result) {
return mysql_fetch_object($result);
}
public function numRows($result) {
return mysql_num_rows($result);
}
public function close() {
return mysql_close($this->_link);
}
}
?>
class Mysql_Database implements Database {
private $_link;
public function connect($server='', $username='', $password='', $new_link=true, $client_flags=0) {
$this->_link = mysql_connect($server, $username, $password, $new_link, $client_flags);
}
public function error() {
return mysql_errno($this->_link);
}
public function errno() {
return mysql_error($this->_link);
}
public function escape($string) {
return mysql_real_escape_string($string, $this->_link);
}
public function query($query) {
return mysql_query($query, $this->_link);
}
public function fetchArray($result, $array_type = MYSQL_BOTH) {
return mysql_fetch_array($result, $array_type);
}
public function fetchRow($result) {
return mysql_fetch_row($result);
}
public function fetchAssoc($result) {
return mysql_fetch_assoc($result);
}
public function fetchObject($result) {
return mysql_fetch_object($result);
}
public function numRows($result) {
return mysql_num_rows($result);
}
public function close() {
return mysql_close($this->_link);
}
}
?>
Dit is de functionaliteit die door de Database interface afgedwongen wordt en elke database class moet bezitten. Er zijn echter veel meer mysql functies dus deze class zou verder uitgebreid kunnen worden om hem beter aan te laten sluiten op de MySQL functionaliteit. Deze selectie van methods is echter voor elke database te implementeren, daarom worden ze afgedwongen door de interface.
De procedurele code om te communiceren met de database zou er nu als volgt uit kunnen zien:
Code
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
<?php
$db = new Mysql_Database();
$db->connect('host', 'username', 'password');
$db->query('USE webshop');
$result = $db->query("SELECT username FROM users");
while($row = $db->fetchAssoc($result)) {
echo($row['username']);
}
?>
$db = new Mysql_Database();
$db->connect('host', 'username', 'password');
$db->query('USE webshop');
$result = $db->query("SELECT username FROM users");
while($row = $db->fetchAssoc($result)) {
echo($row['username']);
}
?>
Als we de classes voor andere databases geschreven hebben, kunnen we in dit voorbeeldje eenvoudig van database wisselen door enkel de eerste regel te veranderen. Voor een postgreSQL database zou dat bijvoorbeeld zo kunnen zijn:
Code
1
2
3
2
3
<?php
$db = new Postgresql_Database();
?>
$db = new Postgresql_Database();
?>
Verschil tussen abstract classes en interfaces
Ze lijken erg op elkaar, maar er zijn een aantal belangrijke verschillen tussen abstract classes en interfaces.
Abstract classes
- Een abstract class kan bepaalde functionaliteit definiëren en de rest overlaten aan de child.
- Een child kan de reeds gedefinieerde methods overschrijven, maar hoeft dat niet.
- De child class moet een logische relatie hebben met de parent.
- Een child kan maximaal een abstract class extenden.
Interfaces
- Een interface kan geen functionalteit bevatten. Het is enkel een definitie van de methods die gebruikt moeten worden.
- De child class moet alle methods uit de interface van functionaliteit voorzien.
- Verschillende niet gerelateerde classes kunnen op een logische manier gegroepeerd worden door een interface.
- Een child kan meerdere interfaces tegelijkertijd implementeren.