Alternatieve manier voor builder pattern.
Dat FormConfig object bevat eigenlijk bijna alle eigenschappen van het formulier.
Het FormConfig object bevat heel veel methods, die allemaal bedoeld zijn om het object de juiste eigenschappen te geven, zoals eventlisteners, datamappers, etc. (Dus bijvoorbeeld addEventListener(), setDataMapper() )
Een formulier wordt gemaakt met een FormBuilder.
Als je dus een formulier wil maken met een builder, dan zou de builder op zijn minst ook alle methods moeten bevatten die FormConfig heeft. Dat is zo omdat je via het FormBuilder object communiceert (addEventListener(), setDataMapper()) om dit weer in de FormConfig te krijgen.
De builder zou dan een doorgeefluik zijn.
Om dit op te lossen, is in het framework de FormBuilder eigenlijk een extender van een FormConfig.
Daardoor krijgt de FormBuilder automatisch de methods die FormConfig heeft.
Uiteindelijk wordt de builder gecloond.
Dit lijkt me heel lelijk, maar het is wel een goede oplossing in plaats van doorgeefluik code.
Hier is een code voorbeeld van wat ik bedoel. Het is heel erg vereenvoudigd.
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
<?
class Form {
private $config; // FormConfig object dat in elk formulier de eigenschappen bevat.
public function __construct(FormConfigInterface $config) {
$this->config = $config;
}
}
class FormConfig {
public $locked = false;
public function addEventListener(...) {
// In elke method zie je deze controle
if($this->locked) {
throw new \Exception('Het object kan nu niet meer veranderd worden...');
}
// ... Ga verder met inhoud van functie.
}
public function setDataMapper(...) {
// In elke method zie je deze controle
if($this->locked) {
throw new \Exception('Het object kan nu niet meer veranderd worden...');
}
// ... Ga verder met functie.
}
// Nog heel veel functies om dit object te vullen.
// De functie om definitief de immutable FormConfig te krijgen:
public function getConfig() {
$conf = clone $this; // Er wordt een kloon van dit object gemaakt.
$conf->locked = true; // Er wordt een variabele op true gezet, die het object immutable maakt.
}
}
class FormBuilder extends FormConfig {
public function add($...) {
// element (sub form) toevoegen
}
public function get($name) {
// ...
}
// andere methods die specifiek aan de FormBuilder zijn
}
?>
class Form {
private $config; // FormConfig object dat in elk formulier de eigenschappen bevat.
public function __construct(FormConfigInterface $config) {
$this->config = $config;
}
}
class FormConfig {
public $locked = false;
public function addEventListener(...) {
// In elke method zie je deze controle
if($this->locked) {
throw new \Exception('Het object kan nu niet meer veranderd worden...');
}
// ... Ga verder met inhoud van functie.
}
public function setDataMapper(...) {
// In elke method zie je deze controle
if($this->locked) {
throw new \Exception('Het object kan nu niet meer veranderd worden...');
}
// ... Ga verder met functie.
}
// Nog heel veel functies om dit object te vullen.
// De functie om definitief de immutable FormConfig te krijgen:
public function getConfig() {
$conf = clone $this; // Er wordt een kloon van dit object gemaakt.
$conf->locked = true; // Er wordt een variabele op true gezet, die het object immutable maakt.
}
}
class FormBuilder extends FormConfig {
public function add($...) {
// element (sub form) toevoegen
}
public function get($name) {
// ...
}
// andere methods die specifiek aan de FormBuilder zijn
}
?>
Code (php)
1
2
3
4
5
6
2
3
4
5
6
// Gebruik van de FormBuilder als je een formulier wil maken
$builder = // FormBuilder instance
$builder->add(...); // Formulier element (in Symfony eigenlijk een sub form).
$builder->addEventListener(...); // Als je een listener wilt toevoegen. Deze method wordt door de builder overge-erft van FormConfig.
// Wanneer het formulier definitief is, wordt ergens in het framework de definitieve, immutable FormConfig gekregen, door FormConfig->getConfig();
$builder = // FormBuilder instance
$builder->add(...); // Formulier element (in Symfony eigenlijk een sub form).
$builder->addEventListener(...); // Als je een listener wilt toevoegen. Deze method wordt door de builder overge-erft van FormConfig.
// Wanneer het formulier definitief is, wordt ergens in het framework de definitieve, immutable FormConfig gekregen, door FormConfig->getConfig();
Wat je hier dus ziet is dat een object wordt gecloond omdat de property variabelen er toe doen, maar de methods niet. Want de methods zijn allemaal functies die het object veranderbaar maken. En dit wil je nu niet meer. Om dus tegen te gaan dat op het definitieve onveranderbare object nog methods worden gebruikt die het object kunnen veranderen, wordt in elke method gecontroleerd of de property locked op true staat. Is dat zo, dan is het huidige object dus tot stand gekomen door een kloon, en mag dus niet veranderd worden.
Dit lijkt me heel lelijk.
- Is er een andere manier om een object immutable te maken, zonder dat je in elke method hoeft te controleren of de wijziging gedaan mag worden? Dit lijkt meer op een handmatige manier van encapsulation.
Normaal gesproken, als je een object instance eigenschappen één keer wil geven, en die eigenschappen mogen daarna niet meer van buiten de class veranderd worden, dan gebruik je een constructur.
Maar voor een object zoals FormConfig, dat misschien 20 eigenschappen kan hebben is het niet te doen om een constructor te gebruiken.
Is dat misschien een reden voor de clone-en-lock manier?
Gewijzigd op 07/03/2017 17:02:10 door Mark Hogeveen
Harry hogeveen op 07/03/2017 16:53:30:
Dit lijkt me heel lelijk.
Wáárom lijkt je dat lelijk?
Als je weet waarom je iets lelijk vindt, weet je ook hoe je het mooier kunt maken…
Dat is niet per definitie zo — bij Kunst met een hoofdletter gaat die vlieger niet op, maar bij objectgeoriënteerd programmeren wel, want je kunt dan zeggen: "Dit is lelijk want je kunt het beter zus-en-zo doen om die-en-die reden."
Je krijgt dan in elke method een terugkomend stukje:
Code (php)
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
<?php
if($this->locked) {
throw new \Exception('Het object kan nu niet meer veranderd worden...');
}
// ... Ga verder met inhoud van functie.
?>
if($this->locked) {
throw new \Exception('Het object kan nu niet meer veranderd worden...');
}
// ... Ga verder met inhoud van functie.
?>
En daarbij, stel ik me bij object cloning iets anders voor. Bij een clone denk ik aan iets dat precies hetzelfde is, maar in werkelijkheid is dat niet zo in dit geval, want direct na de insrtance die je van de kloning krijgt, wordt het object immutable gemaakt d.m.v. de property locked die true wordt.
Dat immmutable maken, zou ik juist het liefste doen door op een of andere manier een access modifier te gebruiken, zoals private methods. Maar de toegangkelijkheid van methods/properties in code (public, private, protected) staat natuurlijk los van een eigen ingebouwde manier om object wijzigingen te blokkeren.
Gewijzigd op 07/03/2017 22:52:15 door Mark Hogeveen
Wat is de gedachte hier achter?
Of een workaround nou foeilelijk is of juist mooi gevonden, lijkt me persoonlijk een kwestie van smaak.
Ik heb er over nagedacht en het lijkt me niet dat er een andere manier is.
Weet je zeker dat je dit alles niet te zeer "voor de vorm" doet en het praktische aspect een beetje uit het oog bent verloren? De insteek moet wel een beetje meewerken in de richting van je doel, nu lijkt het erop dat dit je alleen maar tegenwerkt :p.
Begrijp me niet verkeerd, ik wil niet de draak steken met wat je doet of probeert te bereiken en vind de discussie wel interessant (zij het wat abstract) maar soms kan het handig zijn om even een stapje terug te nemen en de huidige koers nog eens onder de loep te nemen.