Je pense qu'il serait judicieux de séparer cette discussion de celle de
East.
Je vous propose cette petite successions de code. J'aimerai que chacun me dise à quel moment il pense qu'on a quitté "la bonne façon de faire". Pour rappel, je ne parle plus de East, mais bien d'instanceof.
Code PHP :
<?php
public function giveGold(Gold $x) {
$this->gold += $x->getValue();
}
On va dire que c'est le code 0. Je file du Gold à ma classe.
Etape 1: mon algo interne change, si c'est un gros paquet d'or, je dis "youpi"
Code PHP :
<?php
public function giveGold(Gold $x) {
$this->gold += $x->getValue();
if ($x->getValue() > 100)
echo "Youpi!";
}
Etape 2: finalement, je jette les petits paquets d'or par terre, et ne garde que les gros
Code PHP :
<?php
public function giveGold(Gold $x) {
if ($x->getValue() > 100) {
$this->gold += $x->getValue();
echo "Youpi!";
}
else
$x->estJeteParTerre();
}
Etape 3: bof en fait, c'est fatigant de jeter un truc par terre, je vais juste l'ignorer
Code PHP :
<?php
public function giveGold(Gold $x) {
if ($x->getValue() > 100) {
$this->gold += $x->getValue();
echo "Youpi!";
}
}
moi je dirais que l'exemple est trop trivial (gold)
j'enverrai pas un gold, mais un int ou float
mais bon, supposons qu'on soit avec ce "gold"
alors je ne ferais pas du tout comme toi :
$this->gold devrait lui meme être un gold et pas un int
et donc
au lieu d'avoir $this->gold += $x->getValue on aurait
soit $this->gold += $x (en supposant qu'on puisse surcharger += pour une classe en particulier)
soit $this->gold->add ($x)
sinon plus factuellement
pour des cas de comparaison de nombre (genre ici >0) je prends le getter et je compare, et c'est tout
qu'est ce que je vais utiliser des concepts objets poussés pour au final me retrouver avec des notions d'arithmétique de base.
Tout redévelopper ? réinventer la roue ?
pour cela que je trouve l'exemple trop trivial, essaie avec des algo qui soient plus complexes (genre voiture sportive qui plaise à madame)
Okay, voici donc un autre exemple moins trivial. Je vais un peu changer d'approche (cela ferait trop d'étapes pour un exemple moins trivial). Je donne une spec, j'en propose l'implé qui me semble cohérente, et j'aimerai savoir comment vous l'implémenteriez:
Irma, la classe des voyantes, ne peut qu'acheter des Voiture. Elle ne l'achète que si c'est une Ferrari rouge.
Code PHP :
<?php
interface Voiture {
public function getColor();
}
interface Ferrari {
}
class F50 implements Voiture, Ferrari {
private $color;
public function __construct($color) {
$this->color = (string)$color;
}
}
class Irma {
public function acheter(Voiture $voiture) {
if ($voiture instanceof Ferrari && $voiture->getColor() == "rouge") {
echo "J'achète!";
}
}
}
Est-ce que ça vous semble cohérent? Si non, pourquoi? Comment vous l'implémenteriez?
Il n'y a pas besoin de instanceof selon moi.
Puisqu'une Marque n'a pas de comportement elle a aucun intérêt à être dans une interface.
<?php
namespace Voiture;
interface Voiture
{
public function jeVeuxConnaitreTaCouleurEtTaMarque(Acheteur $acheteur);
}
interface Acheteur
{
public function acheter(Voiture $voiture);
public function laMarqueEtLaCouleurDeLaVoitureSont(
Marque $marque,
Couleur $couleur,
Voiture $voiture
);
}
final class Marque
{
private $pattern = '/^[A-Z][a-z]*$/';
/**
* @params string
*/
public function __construct($value)
{
if (is_string($value) === false) {
throw new \RuntimeException('Marque should be construct from a string');
}
if (strlen($value) === 0) {
throw new \RuntimeException('Marque length should be greater than 0');
}
if (preg_match($this->pattern, $value) !== 1) {
throw new \RuntimeException('Marque should match the pattern ' . $this->pattern);
}
$this->value = $value;
}
public function equals(Couleur $couleur)
{
return $this->value === $couleur->value;
}
}
final class Couleur
{
private $pattern = '/^[a-z]*$/';
/**
* @params string
*/
public function __construct($value)
{
if (is_string($value) === false) {
throw new \RuntimeException('Couleur should be construct from a string');
}
if (strlen($value) === 0) {
throw new \RuntimeException('Couleur length should be greater than 0');
}
if (preg_match($this->pattern, $value) !== 1) {
throw new \RuntimeException('Couleur should match the pattern ' . $this->pattern);
}
$this->value = $value;
}
public function equals(Couleur $couleur)
{
return $this->value === $couleur->value;
}
}
namespace Application;
class F50 implements \Voiture\Voiture
{
private $couleur;
private $marque;
public function __construct(\Voiture\Couleur $couleur)
{
$this->couleur = $couleur;
$this->marque = new \Voiture\Marque('Ferrari');
}
public function jeVeuxConnaitreTaCouleurEtTaMarque(\Voiture\Acheteur $voiture)
{
$voiture->laMarqueEtLaCouleurDeLaVoitureSont($this->marque, $this->couleur, $this);
return $this;
}
}
class Irma implements \Voiture\Acheteur
{
private $voitureCouleur;
private $voitureMarque;
public function acheter(\Voiture\Voiture $voiture)
{
$irma = new Irma();
$voiture->jeVeuxConnaitreTaCouleurEtTaMarque($irma);
if (
$irma->voitureCouleur == new \Voiture\Couleur('rouge')
&& $irma->voitureMarque == new \Voiture\Marque('Ferrari')
) {
echo "J'achète\n";
}
return $this;
}
public function laMarqueEtLaCouleurDeLaVoitureSont(
\Voiture\Marque $marque,
\Voiture\Couleur $couleur,
\Voiture\Voiture $voiture
) {
$this->voitureMarque = $marque;
$this->voitureCouleur = $couleur;
return $this;
}
}
$f50 = new F50(new \Voiture\Couleur('rouge'));
$irma = new Irma();
$irma->acheter($f50);
Pourquoi une marque n'aurait-elle pas de comportement? Ou, autrement formulé, pourquoi un comportement (comme passer la 7e vitesse) ne pourrait pas être propre à une marque?
Après, l'interface Voiture::jeVeuxConnaitreTaCouleurEtTaMarque(Acheteur $acheteur) force toute voiture à implémenter cette méthode, qu'elle ait ou non une amrque/couleur (et le nom de méthode interdit d'avoir plusieurs marques).
PS: comme la dernière fois, tu as oublié $irma->voitureCouleur = null; $irma->voitureMarque = null .
Vu que je fais un new Irma je n'ai pas besoin de forcer à null, dans une nouvelle instance il l'est déjà
Bah une marque c'est un valueObject pour moi.
Une marque n'a pas de comportement.
Et si on se dit que la Marque définis le nombre de vitesse, ça n'est toujours pas un comportement mais une caractéristique.
J'ai montré volontairement avec jeVeuxConnaitreTaCouleurEtTaMarque une autre façon de passer les informations, tu peux te donner le droit de passer plusieurs informations à la fois.
Mais comme tu l'as soulevé, c'est moins souple.
C'est un choix à faire à la conception.
Je ne parlais pas de passerLa7eVitesse() comme un nombre de vitesses, mais bien comme un comportement, comme par exemple allumerLeLogoSurLeCapot() ou faireUnBruitDePantèreQuiRugit().
Si tu préfères, remplace la Voiture par le concept Vehicule, et considère que Irma n'achète que les Camion, et dit "crotte" pour le reste. Là, t'auras bien des comportement spécifiques (le Camion aura le comportement attacherRemorque() ou ouvrirPortieres() qu'une Moto n'aura pas).
Dans ce genre de cas, tu ferais comment?
je rejoignais srm
ca sert a rien de créer des interfaces / classes si il n'y a pas un vrai besoin
c'est le souci de tes exemples Xenos, ils sont perturbants.
Après oui si tu as besoin de classes (camion , moto) ou d'interface parce qu'il y a une vraie justification objet (comportement etc) alors oui, je pense qu'utiliser instance of est une bonne chose
maintenant définir tout comme étant des interfaces pour pouvoir faire if instanceof au lieu de if ->Color() == 'blue' ne me semble pas pertinent
tout n'est pas "type" ni "classe/interface"
moi je dis utilisons la richesse du langage sélectionné plutôt que de contraindre à une méthode pour utiliser cette méthode a tout prix
Ton exemple n'est toujours pas très clair pour moi.
J'ai déjà une interface Voiture, donc pourquoi tu me parles de Moto ?
Je ne vais pas faire Moto extends Voiture.
Et si Irma doit pouvoir acheter une Moto je fais :
class Irma implements \Voiture\Acheteur, \Moto\Acheteur
public function acheterMoto(\Moto\Moto $moto)
Code PHP :
<?php
interface Vehicule {
public function getColor();
}
interface Voiture extends Vehicule {
public function circulerEnVille();
}
interface Camion extends Vehicule {
public function attacherRemorque();
}
interface Moto extends Vehicule {
public function roulerSurRoueArriere();
}
class AmericanTruck implements Camion {
}
class Davidson implements Moto {
}
class Irma {
public function acheterVehicule(Vehicule $vehicule) {
if ($vehicule instanceof Camion)
echo "Okay I buy it";
else
echo "I'll only buy it for half its price";
}
}
Ce genre d'exemple est-il plus clair?