[OOP] Beginnen - Wiskundig voorbeeld
Ik heb een class Verzameling gemaakt, en daarna nog een class Verzamelingen.
De bedoeling is zoiets te maken als Venndiagrammen.
Dit is een klein projectje, enkel om van te leren.
Ben ik op de goeie weg met onderstaand voorbeeld?
Ik dacht er misschien aan om alles in 1 class te doen. Maar geef ik dan niet te veel verantwoordelijkheid aan die ene class?
Het is ook fein te weten dat je objecten mee kan geven als parameters. Dat wist ik niet (tot op vandaag).
Code (php)
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
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
<?php
class Verzameling
{
private $elementen;
function __construct($elementen)
{
$this->elementen = $elementen;
}
public function getElementen()
{
return $this->elementen;
}
public function setElementen($elementen)
{
$this->elementen = $elementen;
}
public function controleerElement($element)
{
if(in_array($element, $this->elementen))
{
return true;
}
else
{
return false;
}
}
}
class Verzamelingen extends Verzameling
{
private $verzamelingen, $unie, $doorsnede;
function __construct($verzamelingen)
{
$this->verzamelingen = $verzamelingen;
}
public function berekenUnie()
{
$this->unie = array();
foreach($this->verzamelingen as $verzameling)
{
$this->unie = array_merge($this->unie, $this->LeesElementen($verzameling));
}
return array_unique($this->unie);
}
public function berekenDoorsnede()
{
$this->doorsnede = array();
foreach($this->verzamelingen as $verzameling)
{
$this->doorsnede[] = $this->LeesElementen($verzameling);
}
return call_user_func_array('array_intersect',$this->doorsnede);
}
private function LeesElementen($verzameling)
{
return $verzameling->getElementen();
}
}
$verzamelingA = new Verzameling(array(6));
$verzamelingB = new Verzameling(array(2,4,6,8));
$verzamelingen = new Verzamelingen(array($verzamelingA, $verzamelingB));
$unie = $verzamelingen->berekenUnie();
$doorsnede = $verzamelingen->berekenDoorsnede();
//var_dump($unie);
//var_dump($doorsnede);
?>
class Verzameling
{
private $elementen;
function __construct($elementen)
{
$this->elementen = $elementen;
}
public function getElementen()
{
return $this->elementen;
}
public function setElementen($elementen)
{
$this->elementen = $elementen;
}
public function controleerElement($element)
{
if(in_array($element, $this->elementen))
{
return true;
}
else
{
return false;
}
}
}
class Verzamelingen extends Verzameling
{
private $verzamelingen, $unie, $doorsnede;
function __construct($verzamelingen)
{
$this->verzamelingen = $verzamelingen;
}
public function berekenUnie()
{
$this->unie = array();
foreach($this->verzamelingen as $verzameling)
{
$this->unie = array_merge($this->unie, $this->LeesElementen($verzameling));
}
return array_unique($this->unie);
}
public function berekenDoorsnede()
{
$this->doorsnede = array();
foreach($this->verzamelingen as $verzameling)
{
$this->doorsnede[] = $this->LeesElementen($verzameling);
}
return call_user_func_array('array_intersect',$this->doorsnede);
}
private function LeesElementen($verzameling)
{
return $verzameling->getElementen();
}
}
$verzamelingA = new Verzameling(array(6));
$verzamelingB = new Verzameling(array(2,4,6,8));
$verzamelingen = new Verzamelingen(array($verzamelingA, $verzamelingB));
$unie = $verzamelingen->berekenUnie();
$doorsnede = $verzamelingen->berekenDoorsnede();
//var_dump($unie);
//var_dump($doorsnede);
?>
Met vriendelijke groeten,
Diov
Gewijzigd op 27/09/2014 18:18:37 door - Diov -
Ik vind het wel aardig alleen vraag ik me hier wel af waarom je een base class en een extended class aanmaakt.
Ik snap je argument over verantwoordelijkheden wel maar van de twee classes die je nu hebt kun je dezelfde verantwoordelijkheid noemen: het maken Venndiagrammen (zoals ik het nu bekijk).
In principe als je besluit om een base class te maken dan wil je die (in de toekomst) meerdere keren uitbreiden (extenden). Anders heeft het geen zin.
In een framework is de controller wel een goed voorbeeld.
Code (php)
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
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
<?php
// classes/BaseController.php
class BaseController // BASE CLASS
{
private $template;
function __construct()
{
$this->template = new Template();
}
protected function loadView($template, $data = false)
{
$this->template->setData($data);
return $this->template->Render();
}
}
// controllers/ContactController.php
class ContactController extends BaseController // CONTROLLER VOOR HET CONTACTFORMULIER, UITBREIDING OP BASECONTROLLER
{
public function indexAction()
{
$data = array(
'name' => '',
'email' => '',
'subject' => '',
'message' => ''
);
return $this->loadView('contactform', $data);
}
public function postAction()
{
$data = array(
'name' => $_POST['name'],
'email' => $_POST['email'],
'subject' => $_POST['subject'],
'message' => $_POST['message']
);
return $this->loadView('contactform', $data);
}
}
// controllers/HomeController.php
class HomeController extends BaseController // CONTROLLER VOOR DE HOMEPAGE, UITBREIDING OP BASECONTROLLER
{
public function indexAction()
{
return $this->loadView('homepage');
}
}
?>
// classes/BaseController.php
class BaseController // BASE CLASS
{
private $template;
function __construct()
{
$this->template = new Template();
}
protected function loadView($template, $data = false)
{
$this->template->setData($data);
return $this->template->Render();
}
}
// controllers/ContactController.php
class ContactController extends BaseController // CONTROLLER VOOR HET CONTACTFORMULIER, UITBREIDING OP BASECONTROLLER
{
public function indexAction()
{
$data = array(
'name' => '',
'email' => '',
'subject' => '',
'message' => ''
);
return $this->loadView('contactform', $data);
}
public function postAction()
{
$data = array(
'name' => $_POST['name'],
'email' => $_POST['email'],
'subject' => $_POST['subject'],
'message' => $_POST['message']
);
return $this->loadView('contactform', $data);
}
}
// controllers/HomeController.php
class HomeController extends BaseController // CONTROLLER VOOR DE HOMEPAGE, UITBREIDING OP BASECONTROLLER
{
public function indexAction()
{
return $this->loadView('homepage');
}
}
?>
Toevoeging op 27/09/2014 22:03:08:
In jouw voorbeeld zou ik kiezen voor een class Circle en een class VennDiagram. Dit zijn dan twee totaal verschillende identiteiten dus ga je niet extenden. Wel gaat de class VennDiagram de class Circle meerdere keren gebruiken.
Code (php)
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
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
<?php
class Circle
{
private $offsetTop;
private $offsetLeft;
private $width;
function __construct($offsetTop, $offsetLeft, $width)
{
$this->offsetTop = $offsetTop;
this->offsetLeft = $offsetLeft;
this->width = $width;
}
// meer functies
}
class VennDiagram
{
private $offsetTop;
private $offsetLeft;
private $circles; // array van het object Circle
function __construct($offsetTop, $offsetLeft)
{
$this->offsetTop = $offsetTop;
this->offsetLeft = $offsetLeft;
this->circles = array();
}
public function addCircle($offsetTop, $offsetLeft, $width)
{
$this->circles[] = new Circle($offsetTop, $offsetLeft, $width);
}
// meer functies
}
?>
class Circle
{
private $offsetTop;
private $offsetLeft;
private $width;
function __construct($offsetTop, $offsetLeft, $width)
{
$this->offsetTop = $offsetTop;
this->offsetLeft = $offsetLeft;
this->width = $width;
}
// meer functies
}
class VennDiagram
{
private $offsetTop;
private $offsetLeft;
private $circles; // array van het object Circle
function __construct($offsetTop, $offsetLeft)
{
$this->offsetTop = $offsetTop;
this->offsetLeft = $offsetLeft;
this->circles = array();
}
public function addCircle($offsetTop, $offsetLeft, $width)
{
$this->circles[] = new Circle($offsetTop, $offsetLeft, $width);
}
// meer functies
}
?>
* "extends" geeft een IS_EEN relatie aan. Je moet de extends dus kunnen vervangen met IS_EEN. Bijv. PDO IS_EEN DatabaseAbstractionLayer. In jouw geval: Verzamelingen IS_EEN Verzameling. Dat klopt niet, een meervoud van iets kan nooit gelijk zijn aan het iets zelf.
* Als je extend erft de klasse die extend alle verantwoordelijkheden van zijn parent over. In dit geval heb je dus hetzelfde als dat je maar 1 klasse zou maken.
Wat ik me erg afvraag is of er hier uberhaupt wel spraken is van 2 verantwoordelijkheden.
Laten we beginnen met het primaire object in deze situatie: Set (ik raad aan altijd in het engels te werken). Deze Set klasse is een zogenaamde Value Object, het houdt een bepaalde waarde vast. Een kenmerk van een Value Object is dat hij immutable is: Je kan zijn data na het initialiseren niet meer aanpassen. Dat klopt ook in dit geval, wanneer een Set is aangemaakt kan hij niet meer worden aangepast. Ja, we kunnen hem "mergen" met een andere Set en zo een nieuwe Set maken, maar de vorige 2 Sets zijn dan niet aangepast. Onze Set klasse heeft dus geen setElements/addElements, slechts alleen een mogelijkheid om elementen in de constructor in te stellen en een getter om de elementen te verkrijgen:
Code (php)
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php
class Set
{
private $elements;
public function __construct(array $elements)
{
$this->elements = $elements;
}
public function getElements()
{
return $this->elements;
}
public function __toString()
{
return '{'.implode(', ', $this->getElements()).'}';
}
}
$set = new Set([1, 2, 3]);
echo $set; // {1, 2, 3}
?>
class Set
{
private $elements;
public function __construct(array $elements)
{
$this->elements = $elements;
}
public function getElements()
{
return $this->elements;
}
public function __toString()
{
return '{'.implode(', ', $this->getElements()).'}';
}
}
$set = new Set([1, 2, 3]);
echo $set; // {1, 2, 3}
?>
Oke, dat is de basis. Nu kan deze Value Object ook nog wat logica omtrent de waarde die hij vasthoudt uitvoeren. Dat is geen aparte verantwoordelijkheid. Een aparte verantwoordelijkheid betekend dat je de klasse moet aanpassen om meer dan 1 rede. Op deze manier kunnen we dus een actie toevoegen waarbij we kijken of een waarde in een set voorkomt:
Code (php)
Ook kunnen we de 2 merge strategies voor een Set invoegen: union en intersection. Merk op dat we hierbij dus niet de huidige Set moeten aanpassen, deze is namelijk immutable. We moeten dus een nieuwe Set teruggeven met de waardes van beide sets.
Code (php)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
class Set
{
// ...
public function unite(Set $other)
{
return new Set(array_merge($this->getElements(), $other->getElements());
}
public function intersect(Set $other)
{
return new Set(array_intersect($this->getElements(), $other));
}
}
$set1 = new Set([1, 2, 3]);
$set2 = new Set([3, 4, 5]);
echo $set1->unite($set2); // {1, 2, 3, 3, 4, 5}
echo $set1; // {1, 2, 3}
echo $set2->intersect($set1); // {3}
?>
class Set
{
// ...
public function unite(Set $other)
{
return new Set(array_merge($this->getElements(), $other->getElements());
}
public function intersect(Set $other)
{
return new Set(array_intersect($this->getElements(), $other));
}
}
$set1 = new Set([1, 2, 3]);
$set2 = new Set([3, 4, 5]);
echo $set1->unite($set2); // {1, 2, 3, 3, 4, 5}
echo $set1; // {1, 2, 3}
echo $set2->intersect($set1); // {3}
?>
Als laatste kunnen we bijv. nog iets maken dat 2 lijsten vergelijkt:
Code (php)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
class Set
{
// ...
public function equals(Set $other)
{
return array_unique($this->getElements()) === array_unique($other->getElements());
}
}
$set1 = new Set([1, 2, 3]);
$set2 = $set1->unite($set1);
echo $set2; // {1, 2, 3, 1, 2, 3}
$set1->equals($set2); // true (dubbele waarden worden weggelaten)
?>
class Set
{
// ...
public function equals(Set $other)
{
return array_unique($this->getElements()) === array_unique($other->getElements());
}
}
$set1 = new Set([1, 2, 3]);
$set2 = $set1->unite($set1);
echo $set2; // {1, 2, 3, 1, 2, 3}
$set1->equals($set2); // true (dubbele waarden worden weggelaten)
?>
Al deze acties hebben te maken met Sets en mogen dus gewoon ondergebracht worden in de Set Value Object, zolang we maar zorgen dat we immutable blijven. Ga we echter Sets toepassen, dan behoort die actie niet meer tot een actie van de Set, maar een actie met de Set. Als ik bijv. wil kijken of een bepaalde Set echt de x waardes zijn voor een vergelijking als f(x) = 0 dan behoort deze actie niet meer tot de Set. Deze zou dan bijv. tot een FunctionSolver klasse behoren:
Code (php)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
class FunctionSolver
{
public function isSetAnswer(callable $function, callable $equation, Set $set)
{
foreach ($set->getElements() as $element) {
if (false === $equation($function($element))) {
return false;
}
}
return true;
}
}
$solver = new FunctionSolver();
$solver->isSetAnswer(
function ($x) { return pow($x, 2); }, // f(x) = x^2
function ($y) { return $y == 4; }, // f(x) = 4
new Set([-2, 2])
); // true, f(-2) = 4 en f(2) = 4
?>
class FunctionSolver
{
public function isSetAnswer(callable $function, callable $equation, Set $set)
{
foreach ($set->getElements() as $element) {
if (false === $equation($function($element))) {
return false;
}
}
return true;
}
}
$solver = new FunctionSolver();
$solver->isSetAnswer(
function ($x) { return pow($x, 2); }, // f(x) = x^2
function ($y) { return $y == 4; }, // f(x) = 4
new Set([-2, 2])
); // true, f(-2) = 4 en f(2) = 4
?>
Het extenden van classes gebeurt veel minder vaak dan men vaak denkt. Eigenlijk als je net begint met OOP zou je heel dat woord 'extends' moeten vergeten.