Lors de la modélisation des classes, quel est le moyen préféré d’initialisation:
Et quelles seraient les considérations pour utiliser l'un ou l'autre?
Dans certaines situations, je préfère avoir une méthode de fabrique qui renvoie null si l'objet ne peut pas être construit. Cela rend le code soigné. Je peux simplement vérifier si la valeur renvoyée n'est pas nulle avant de prendre une action alternative, par opposition à une exception du constructeur. (Personnellement, je n'aime pas les exceptions)
Dis, j'ai un constructeur sur une classe qui attend une valeur id. Le constructeur utilise cette valeur pour renseigner la classe à partir de la base de données. Dans le cas où un enregistrement avec l'ID spécifié n'existe pas, le constructeur lève une exception RecordNotFoundException. Dans ce cas, je devrai inclure la construction de toutes ces classes dans un bloc try..catch.
Contrairement à cela, je peux avoir une méthode fabrique statique sur ces classes qui renverra null si l'enregistrement n'est pas trouvé.
Quelle approche est la meilleure dans ce cas, méthode constructeur ou méthode usine?
À partir de la page 108 de Modèles de conception: Éléments d'un logiciel orienté objet réutilisable de Gamma, Helm, Johnson et Vlissides
Utilisez le modèle de méthode d'usine lorsque
Demandez-vous ce qu'ils sont et pourquoi nous les avons. Ils sont tous deux là pour créer une instance d'un objet.
ElementarySchool school = new ElementarySchool();
ElementarySchool school = SchoolFactory.Construct(); // new ElementarySchool() inside
Aucune différence jusqu'à présent. Maintenant, imaginons que nous avons différents types d’écoles et que nous souhaitons passer de ElementarySchool à HighSchool (dérivé d’un ElementarySchool ou implémentant la même interface ISchool que ElementarySchool). Le changement de code serait:
HighSchool school = new HighSchool();
HighSchool school = SchoolFactory.Construct(); // new HighSchool() inside
En cas d'interface nous aurions:
ISchool school = new HighSchool();
ISchool school = SchoolFactory.Construct(); // new HighSchool() inside
Maintenant, si vous avez ce code à plusieurs endroits, vous pouvez voir que l’utilisation de la méthode factory risque d’être assez économique, car une fois que vous avez modifié la méthode factory, vous avez terminé (si nous utilisons le second exemple avec des interfaces).
Et c'est la principale différence et avantage. Lorsque vous commencez à traiter avec des hiérarchies de classes complexes et que vous souhaitez créer dynamiquement une instance d'une classe à partir d'une telle hiérarchie, vous obtenez le code suivant. Les méthodes d'usine peuvent ensuite prendre un paramètre qui indique à la méthode quelle instance concrète doit être instanciée. Supposons que vous ayez une classe MyStudent et que vous deviez instancier l'objet ISchool correspondant pour que votre élève soit membre de cette école.
ISchool school = SchoolFactory.ConstructForStudent(myStudent);
Vous avez maintenant un emplacement dans votre application qui contient la logique métier qui détermine quel objet ISchool doit instancier pour différents objets IStudent.
Donc, pour les classes simples (objets de valeur, etc.), le constructeur convient parfaitement (vous ne voulez pas trop modifier votre application), mais pour les hiérarchies de classes complexes, la méthode de fabrique est un moyen privilégié.
De cette façon, vous suivez le premier principe de conception de la bande de quatre livres "Programme pour une interface, pas une implémentation".
Vous devez lire (si vous avez accès à) Effective Java 2Élément 1: considérez les méthodes de fabrique statique à la place des constructeurs.
Avantages des méthodes d'usine statiques:
Méthodes d'usine statiques inconvénients:
Par défaut, les constructeurs doivent être préférés, car ils sont plus simples à comprendre et à écrire. Cependant, si vous devez spécifiquement dissocier les subtilités de construction d'un objet de son sens sémantique tel que le comprend le code client, vous feriez mieux d'utiliser des usines.
La différence entre constructeurs et usines est analogue à, par exemple, une variable et un pointeur sur une variable. Il existe un autre niveau d'indirection, ce qui est un inconvénient; mais il existe aussi un autre niveau de flexibilité, ce qui est un avantage. Ainsi, tout en faisant un choix, vous seriez bien avisé de faire cette analyse coûts/avantages.
Utilisez une fabrique uniquement lorsque vous avez besoin d'un contrôle supplémentaire avec la création d'objet, ce qui était impossible avec les constructeurs.
Les usines ont la possibilité de mettre en cache par exemple.
Une autre façon d'utiliser les usines consiste à utiliser un scénario dans lequel vous ne connaissez pas le type que vous voulez construire. Vous voyez souvent ce type d'utilisation dans les scénarios d'usine de plug-ins, où chaque plug-in doit provenir d'une classe de base ou implémenter une sorte d'interface. La fabrique crée des instances de classes dérivées de la classe de base ou qui implémentent l'interface.
Cite de "Effective Java", 2e éd., Élément 1: considérez les méthodes d'usine statiques au lieu des constructeurs, p. 5:
"Notez que une méthode d'usine statique n'est pas la même chose que le modèle de méthode d'usine De Design Patterns [Gamma95, p. 107]. La méthode d'usine statique décrite dans Modèles de conception."
Un exemple concret d'une application de CAO/FAO.
Un chemin de découpe serait créé en utilisant un constructeur. C'est une série de lignes et d'arcs définissant un chemin à couper. Bien que la série de lignes et d'arcs puisse être différente et avoir des coordonnées différentes, elle est facilement gérée en passant une liste à un constructeur.
Une forme serait serait faite en utilisant une usine. Parce qu’il existe une classe de formes, chaque forme est configurée différemment selon son type. Nous ne savons pas quelle forme nous allons initialiser tant que l'utilisateur n'aura pas fait sa sélection.
Outre "effective Java" (comme indiqué dans une autre réponse), un autre livre classique suggère également:
Préférez les méthodes de fabrique statiques (avec des noms décrivant les arguments) aux constructeurs surchargés.
Par exemple. n'écris pas
Complex complex = new Complex(23.0);
mais au lieu d'écrire
Complex complex = Complex.fromRealNumber(23.0);
Le livre va jusqu'à suggérer de rendre le constructeur Complex(float)
privé, afin de forcer l'utilisateur à appeler la méthode fabrique statique.
Dis, j'ai un constructeur sur une classe qui attend une valeur id. Le constructeur utilise cette valeur pour renseigner la classe à partir de la base de données.
Ce processus devrait certainement être en dehors d'un constructeur.
Le constructeur ne devrait pas accéder à la base de données.
La tâche et la raison d’un constructeur sont de initialiser les membres de données et de d’établir une classe invariante à l’aide de valeurs passées au constructeur.
Pour tout le reste, une meilleure approche consiste à utiliser la méthode static factory ou, dans les cas plus complexes, une classe séparée factory ou builder.
Quelques guides de constructeur de Microsoft :
Faites un travail minimal dans le constructeur. Les constructeurs ne doivent pas faire grand chose d'autre que capturer les paramètres du constructeur. Le coût de tout autre traitement devrait être retardé jusqu'au moment requis.
Et
Envisagez d'utiliser une méthode fabrique statique au lieu d'un constructeur si la sémantique de l'opération souhaitée ne correspond pas directement à la construction d'une nouvelle instance.
Parfois, vous devez vérifier/calculer des valeurs/conditions lors de la création d'un objet. Et s’il peut lancer une exception - le constructeur est très mauvais. Donc, vous devez faire quelque chose comme ça:
var value = new Instance(1, 2).init()
public function init() {
try {
doSome()
}
catch (e) {
soAnotherSome()
}
}
Où tous les calculs supplémentaires sont dans init (). Mais vous seul, en tant que développeur, connaissez réellement init (). Et bien sûr, après des mois, vous oubliez simplement cela… .. Mais si vous avez une usine - faites tout ce que vous avez besoin dans une méthode en masquant cet init () de l'appel direct - donc pas de problèmes. Avec cette approche, il n’ya aucun problème avec la création et les fuites de mémoire.
Quelqu'un vous a parlé de la mise en cache. C'est bon. Mais vous devez également vous rappeler le motif Flyweight qui est agréable à utiliser avec la méthode Factory.