C'est assez simple, en fait:
Au lieu d'avoir un constructeur qui fait votre configuration,
// c-family pseudo-code
public class Thing {
public Thing (a, b, c, d) { this.x = a; this.y = b; /* ... */ }
}
... demandez à votre constructeur de faire peu ou rien du tout, et écrivez une méthode appelée .init
ou .initialize
, qui ferait ce que votre constructeur ferait normalement.
public class Thing {
public Thing () {}
public void initialize (a, b, c, d) {
this.x = a; /*...*/
}
}
Alors maintenant, au lieu de simplement faire comme:
Thing thing = new Thing(1, 2, 3, 4);
Tu peux y aller:
Thing thing = new Thing();
thing.doSomething();
thing.bind_events(evt_1, evt_2);
thing.initialize(1, 2, 3, 4);
L'avantage est que vous pouvez désormais utiliser plus facilement l'injection de dépendance / l'inversion de contrôle dans vos systèmes.
Au lieu de dire
public class Soldier {
private Weapon weapon;
public Soldier (name, x, y) {
this.weapon = new Weapon();
}
}
Vous pouvez construire le soldat, lui donner une méthode équipez, où vous remettez lui une arme, et appeler alors tous le reste des fonctions constructeur.
Alors maintenant, au lieu de sous-classer les ennemis où un soldat a un pistolet et un autre a un fusil et un autre a un fusil de chasse, et c'est la seule différence, vous pouvez simplement dire:
Soldier soldier1 = new Soldier(),
soldier2 = new Soldier(),
soldier3 = new Soldier();
soldier1.equip(new Pistol());
soldier2.equip(new Rifle());
soldier3.equip(new Shotgun());
soldier1.initialize("Bob", 32, 48);
soldier2.initialize("Doug", 57, 200);
soldier3.initialize("Mike", 92, 30);
Même chose pour la destruction. Si vous avez des besoins particuliers (suppression d'écouteurs d'événements, suppression d'instances de tableaux / quelles que soient les structures avec lesquelles vous travaillez, etc.), vous les appellerez manuellement, afin de savoir exactement quand et où dans le programme qui se passait.
ÉDITER
Comme Kryotan l'a souligné, ci-dessous, cela répond au "Comment" du post original , mais ne fait pas vraiment un bon travail de "Pourquoi".
Comme vous pouvez probablement le voir dans la réponse ci-dessus, il pourrait ne pas y avoir beaucoup de différence entre:
var myObj = new Object();
myObj.setPrecondition(1);
myObj.setOtherPrecondition(2);
myObj.init();
et l'écriture
var myObj = new Object(1,2);
tout en ayant une fonction constructeur plus grande.
Il y a un argument à faire pour les objets qui ont 15 ou 20 conditions préalables, ce qui rendrait un constructeur très, très difficile à travailler, et cela rendrait les choses plus faciles à voir et à retenir, en tirant ces choses dans l'interface , afin que vous puissiez voir comment l'instanciation fonctionne, un niveau plus haut.
La configuration optionnelle des objets en est une extension naturelle; définir éventuellement des valeurs sur l'interface, avant d'exécuter l'objet.
JS a de très bons raccourcis pour cette idée, qui semblent tout simplement hors de propos dans les langages de type c plus forts.
Cela dit, il y a des chances, si vous avez affaire à une liste d'arguments aussi longue dans votre constructeur, que votre objet est trop grand et fait trop, tel quel. Encore une fois, c'est une chose de préférence personnelle, et il y a des exceptions partout, mais si vous passez 20 choses dans un objet, il y a de bonnes chances que vous puissiez trouver un moyen de faire en sorte que cet objet fasse moins, en faisant des objets plus petits .
Une raison plus pertinente, et largement applicable, serait que l'initialisation d'un objet repose sur des données asynchrones, que vous n'avez pas actuellement.
Vous savez que vous avez besoin de l'objet, vous allez donc le créer de toute façon, mais pour qu'il fonctionne correctement, il a besoin des données du serveur ou d'un autre fichier qu'il doit maintenant charger.
Encore une fois, que vous passiez les données nécessaires dans un gigantesque init ou que vous construisiez une interface n'est pas vraiment important pour le concept, autant qu'il l'est pour l'interface de votre objet et la conception de votre système ...
Mais en termes de construction de l'objet, vous pourriez faire quelque chose comme ceci:
var obj_w_async_dependencies = new Object();
async_loader.load(obj_w_async_dependencies.async_data, obj_w_async_dependencies);
async_loader
peut obtenir un nom de fichier, un nom de ressource ou autre, charger cette ressource - peut-être qu'il charge des fichiers audio ou des données d'image, ou peut-être qu'il charge des statistiques de caractères enregistrées ...
... et ensuite il alimenterait ces données obj_w_async_dependencies.init(result);
.
Ce type de dynamique se retrouve fréquemment dans les applications Web.
Pas nécessairement dans la construction d'un objet, pour les applications de niveau supérieur: par exemple, les galeries peuvent se charger et s'initialiser tout de suite, puis afficher les photos au fur et à mesure - ce n'est pas vraiment une initialisation asynchrone, mais là où elle est vue plus fréquemment, ce serait dans les bibliothèques JavaScript.
Un module peut dépendre d'un autre, et donc l'initialisation de ce module peut être différée jusqu'à ce que le chargement des dépendants soit terminé.
En termes d'instances spécifiques au jeu, considérez une Game
classe réelle .
Pourquoi ne pouvons-nous pas appeler .start
ou .run
dans le constructeur?
Les ressources doivent être chargées - le reste de tout a été à peu près défini et il est bon d'y aller, mais si nous essayons d'exécuter le jeu sans connexion à la base de données, ou sans textures ou modèles ou sons ou niveaux, cela ne sera pas un jeu particulièrement intéressant ...
... alors quelle est la différence entre ce que nous voyons d'un typique Game
, sauf que nous donnons à sa méthode "go ahead" un nom qui est plus intéressant que .init
(ou inversement, casser l'initialisation encore plus loin, pour séparer le chargement, mise en place des choses qui ont été chargées, et exécution du programme lorsque tout a été mis en place).