Pouvons-nous vivre sans constructeurs?


25

Disons que pour une raison quelconque, tous les objets sont créés de cette façon $ obj = CLASS :: getInstance (). Ensuite, nous injectons des dépendances à l'aide de setters et effectuons le démarrage de l'initialisation à l'aide de $ obj-> initInstance (); Y a-t-il de vrais problèmes ou situations qui ne peuvent pas être résolus si nous n'utilisons pas du tout de constructeurs?

Ps la raison de créer un objet de cette façon, c'est que nous pouvons remplacer la classe à l'intérieur de getInstance () selon certaines règles.

Je travaille en PHP, si c'est important


quel langage de programmation?
gnat

2
PHP (pourquoi est-ce important?)
Axel Foley

8
On dirait que ce que vous voulez faire peut être réalisé en utilisant le modèle Factory.
superM

1
Juste comme une note: le patron d'usine @superM mentionné est une façon d'implémenter l'inversion de contrôle (injection de dépendance) en est une autre.
Joachim Sauer

N'est-ce pas à peu près ce que javascript fait avec les objets?
Thijser

Réponses:


44

Je dirais que cela entrave considérablement votre espace de conception.

Les constructeurs sont un excellent endroit pour initialiser et valider les paramètres transmis. Si vous ne pouvez plus les utiliser pour cela, alors l'initialisation, la gestion des états (ou tout simplement le refus du constructeur d'objets "cassés") devient beaucoup plus difficile et partiellement impossible.

Par exemple, si chaque Fooobjet a besoin d'un, Frobnicatoril peut archiver son constructeur si le Frobnicatorn'est pas nul. Si vous enlevez le constructeur, il devient plus difficile à vérifier. Vérifiez-vous à chaque point où il serait utilisé? Dans une init()méthode (externaliser efficacement la méthode constructeur)? Ne le vérifiez jamais et espérez le meilleur?

Bien que vous puissiez probablement tout mettre en œuvre (après tout, vous êtes toujours en train de terminer), certaines choses seront beaucoup plus difficiles à faire.

Personnellement, je suggérerais d'examiner l' injection de dépendance / l' inversion du contrôle . Ces techniques permettent également de changer de classe d'implémentation concrète, mais elles n'empêchent pas l'écriture / l'utilisation de constructeurs.


Qu'entendiez-vous par "ou dénier clairement le constructeur d'objets" cassés ""?
Geek

2
@Geek: un constructeur peut inspecter son argument et décider si ceux-ci aboutiraient à un objet de travail (par exemple si votre objet a besoin d'un, HttpClientil vérifie si ce paramètre n'est pas nul). Et si ces contraintes ne sont pas respectées, il peut alors lever une exception. Ce n'est pas vraiment possible avec l'approche des valeurs de construction et de définition.
Joachim Sauer

1
Je pense que OP décrivait juste l'externalisation du constructeur à l'intérieur init(), ce qui est tout à fait possible, bien que cela impose simplement une charge de maintenance plus importante.
Peter

26

2 avantages pour les constructeurs:

Les constructeurs permettent d'effectuer les étapes de construction d'un objet de manière atomique.

Je pourrais éviter un constructeur et utiliser des setters pour tout, mais qu'en est-il des propriétés obligatoires comme l'a suggéré Joachim Sauer? Avec un constructeur, un objet possède sa propre logique de construction afin de s'assurer qu'il n'y a pas d'instances invalides d'une telle classe .

Si la création d'une instance de Foorequiert la définition de 3 propriétés, le constructeur peut prendre une référence aux 3, les valider et lever une exception si elles ne sont pas valides.

Encapsulation

En s'appuyant uniquement sur les poseurs, la charge incombe au consommateur d'un objet de le construire correctement. Il peut exister différentes combinaisons de propriétés valides.

Par exemple, chaque Fooinstance a besoin d'une instance de Baras property barou d'une instance de BarFinderas property barFinder. Il peut utiliser l'un ou l'autre. Vous pouvez créer un constructeur pour chaque ensemble valide de paramètres et appliquer les conventions de cette façon.

La logique et la sémantique des objets vivent dans l'objet lui-même. C'est une bonne encapsulation.


15

Oui, vous pouvez vivre sans constructeurs.

Bien sûr, vous pouvez vous retrouver avec beaucoup de code de plaque de chaudière dupliqué. Et si votre application est de n'importe quelle échelle, vous passerez probablement beaucoup de temps à essayer de localiser la source d'un problème lorsque ce code passe-partout n'est pas utilisé de manière cohérente dans l'application.

Mais non, vous n'avez pas strictement besoin de vos propres constructeurs. Bien sûr, vous n'avez pas non plus strictement besoin de classes et d'objets.

Maintenant, si votre objectif est d'utiliser une sorte de modèle d'usine pour la création d'objets, cela ne s'exclut pas mutuellement d'utiliser des constructeurs lors de l'initialisation de vos objets.


Absolument. On peut même vivre sans classes ni objets, pour commencer.
JensG

5
Salut à tous le puissant assembleur!
Davor Ždralo

9

L'avantage d'utiliser des constructeurs est qu'ils permettent de garantir plus facilement que vous n'aurez jamais d'objet invalide.

Un constructeur vous donne la possibilité de définir toutes les variables membres de l'objet dans un état valide. Lorsque vous vous assurez qu'aucune méthode de mutation ne peut changer l'objet en un état non valide, vous n'aurez jamais d'objet non valide, ce qui vous évitera de nombreux bogues.

Mais lorsqu'un nouvel objet est créé dans un état non valide et que vous devez appeler certains setters pour le mettre dans un état valide dans lequel il peut être utilisé, vous risquez qu'un consommateur de la classe oublie d'appeler ces setters ou les appelle incorrectement et vous vous retrouvez avec un objet invalide.

Une solution de contournement pourrait être de créer des objets uniquement via une méthode d'usine qui vérifie la validité de chaque objet qu'il crée avant de le renvoyer à l'appelant.


3

$ obj = CLASS :: getInstance (). Ensuite, nous injectons des dépendances à l'aide de setters et effectuons le démarrage de l'initialisation à l'aide de $ obj-> initInstance ();

Je pense que vous rendez cela plus difficile que nécessaire. Nous pouvons très bien injecter des dépendances via le constructeur - et si vous en avez beaucoup, utilisez simplement une structure de type dictionnaire pour pouvoir spécifier celles que vous souhaitez utiliser:

$obj = new CLASS(array(
    'Frobnicator' => (),
    'Foonicator' => (),
));

Et au sein du constructeur, vous pouvez assurer la cohérence comme suit:

if (!array_key_exists('Frobnicator', $args)) {
    throw new Exception('Frobnicator required');
}
if (!array_key_exists('Foonicator', $args)) {
    $args['Foonicator'] = new DefaultFoonicator();
}

$args peut ensuite être utilisé pour définir des membres privés si nécessaire.

Une fois fait entièrement dans le constructeur comme tel, il n'y aura jamais un état intermédiaire où il $objexiste mais n'est pas initialisé, comme ce serait le cas dans le système décrit dans la question. Il vaut mieux éviter de tels états intermédiaires, car vous ne pouvez pas garantir que l'objet sera toujours utilisé correctement.


2

Je pensais en fait à des choses similaires.

La question que j'ai posée était "Quel constructeur fait, et est-il possible de le faire différemment?" Je suis arrivé à ces conclusions:

  • Il garantit que certaines propriétés sont initialisées. En les acceptant comme paramètres et en les définissant. Mais cela pourrait facilement être appliqué par le compilateur. En annotant simplement les champs ou les propriétés comme "requis", le compilateur vérifierait lors de la création de l'instance si tout est correctement défini. L'appel à créer l'instance serait probablement le même, il n'y aurait tout simplement pas de méthode constructeur.

  • Garantit que les propriétés sont valides. Cela pourrait être facilement réalisé par l'affirmation de la condition. Encore une fois, vous annoteriez simplement les propriétés avec des conditions correctes. Certaines langues le font déjà.

  • Une logique de construction plus complexe. Les modèles modernes ne recommandent pas de le faire dans le constructeur, mais proposent d'utiliser des méthodes ou des classes d'usine spécialisées. Ainsi, l'utilisation de constructeurs dans ce cas est minime.

Donc pour répondre à votre question: oui, je crois que c'est possible. Mais cela nécessiterait de grands changements dans la conception du langage.

Et je viens de remarquer que ma réponse est plutôt OT.


2

Oui, vous pouvez presque tout faire sans utiliser de constructeurs, mais cela gaspille clairement les avantages des langages de programmation orientée objet.

Dans les langages modernes (je parlerai ici de C # dans lequel je programme), vous pouvez limiter les parties de code qui ne peuvent être exécutées que dans un constructeur. Grâce à laquelle vous pouvez éviter les erreurs maladroites. L'une de ces choses est le modificateur en lecture seule :

public class A {
    readonly string rostring;

    public A(string arg) {
        rostring = arg;
    }

    public static A CreateInstance(string arg) {
        var result = new A();
        A.rostring = arg;  // < because of this the code won't compile!
        return result;
    }
}

Comme recommandé par Joachim Sauer précédemment au lieu d'utiliser le Factorydesign design, lisez la suite Dependency Injection. Je recommanderais la lecture de l' injection de dépendance dans .NET par Mark Seemann .


1

Instancier un objet avec un type en fonction des besoins c'est absolument possible. Il peut s'agir de l'objet lui-même, utilisant des variables globales du système pour renvoyer un type spécifique.

Cependant, avoir une classe dans le code qui peut être "All" est le concept de type dynamique . Personnellement, je crois que cette approche crée une incohérence dans votre code, rend les complexes de test * et "l'avenir devient incertain" en ce qui concerne le flux du travail proposé.

* Je fais référence au fait que les tests doivent considérer d'abord le type, ensuite le résultat que vous souhaitez obtenir. Ensuite, vous créez un grand test imbriqué.


1

Pour équilibrer certaines des autres réponses, en affirmant:

Un constructeur vous donne la possibilité de mettre toutes les variables membres de l'objet à un état valide ... vous n'aurez jamais d'objet invalide, ce qui vous évitera de nombreux bugs.

et

Avec un constructeur, un objet possède sa propre logique de construction afin de s'assurer qu'il n'y a pas d'instances invalides d'une telle classe.

De telles déclarations impliquent parfois l'hypothèse que:

Si une classe a un constructeur qui, au moment où elle se termine, a mis l'objet dans un état valide, et qu'aucune des méthodes de la classe ne mute cet état pour le rendre invalide, alors il est impossible pour le code en dehors de la classe de détecter un objet de cette classe dans un état non valide.

Mais ce n'est pas tout à fait vrai. La plupart des langages n'ont pas de règle interdisant à un constructeur de passer this( selfou quel que soit le langage qu'il appelle) à du code externe. Un tel constructeur respecte complètement la règle énoncée ci-dessus, et pourtant il risque d'exposer des objets semi-construits à du code externe. C'est un point mineur mais facilement ignoré.


0

C'est quelque peu anecdotique, mais je réserve généralement des constructeurs à l'état essentiel pour que l'objet soit complet et utilisable. Sauf injection de setter, une fois le constructeur exécuté, mon objet devrait pouvoir effectuer les tâches qui lui sont demandées.

Tout ce qui peut être différé, je laisse de côté le constructeur (préparation des valeurs de sortie, etc.). Avec cette approche, je pense ne pas utiliser de constructeurs pour quoi que ce soit, mais l'injection de dépendances a du sens.

Cela a également l'avantage supplémentaire de câbler durement votre processus de conception mentale pour ne rien faire prématurément. Vous n'initialiserez pas ou n'effectuerez pas de logique qui pourrait ne jamais être utilisée, car au mieux tout ce que vous faites est une configuration de base pour le travail à venir.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.