24 votes

La logique métier dans les constructeurs est-elle une bonne idée ?

Je suis actuellement en train de reconstruire un système de tickets spécialisé au travail (principalement utilisé pour aider les personnes ayant des défauts dans le matériel de télédétection...). Bref, je me demandais si faire beaucoup d'activités de type workflow dans le constructeur d'un objet était une bonne idée.

Par exemple, il y a actuellement ceci :

$ticket = new SupportTicket(
    $customer,
    $title,
    $start_ticket_now,
    $mail_customer
);

dès que l'objet est créé, il va placer une ligne dans une base de données, aller envoyer un e-mail de confirmation au client, éventuellement envoyer un SMS au technicien le plus proche, etc.

Est-ce qu'un constructeur devrait déclencher tout ce travail, ou plutôt quelque chose comme ce qui suit ?

$ticket = new SupportTicket($customer, $title);
$customer->confirmTicketMailed($ticket);
$helpdesk->alertNewTicket($ticket);

Si cela peut vous aider, les objets sont tous basés sur le style ActiveRecord.

Je suppose que c'est une question d'opinion, mais quelle est, selon vous, la meilleure chose à faire ?

54voto

Uncle Bob Points 3276

Si le constructeur fait tout ce travail, alors le constructeur connaît de nombreux autres objets du domaine. Cela crée un problème de dépendance. Est-ce que le ticket connaissent vraiment le Customer et le HelpDesk ? Lorsque de nouvelles fonctionnalités sont ajoutées, n'est-il pas probable que de nouveaux objets de domaine soient ajoutés au flux de travail, et cela ne signifie-t-il pas que notre pauvre ticket devra connaître une population toujours plus importante d'objets du domaine ?

Le problème avec les réseaux de dépendance de ce type est qu'une modification du code source de n'importe quel objet du domaine aura un impact sur notre pauvre ticket . Le site ticket aura tellement connaissance du système que, quoi qu'il arrive, le ticket seront impliqués. Vous trouverez de vilaines if se rassemblant à l'intérieur de ce constructeur, vérifiant la base de données de configuration, et l'état de la session, et bien d'autres choses. Le site ticket se développera pour devenir une classe divine.

Une autre raison pour laquelle je n'aime pas les constructeurs qui font des choses est que cela rend les objets qui les entourent très difficiles à tester. J'aime écrire beaucoup d'objets fantaisie. Lorsque j'écris un test contre le customer Je veux le faire passer pour un faux. ticket . Si le constructeur de ticket contrôle le flux de travail, et la danse entre customer et d'autres objets du domaine, il est peu probable que je puisse le simuler pour tester l'interface utilisateur de l'entreprise. customer .

Je vous suggère de lire Les principes de SOLID J'ai écrit un article il y a plusieurs années sur la gestion des dépendances dans les conceptions orientées objet.

4voto

Christophe Herreman Points 11844

Divisez les choses. Vraiment, vous ne voulez pas qu'un ticket sache comment il doit envoyer un e-mail, etc. C'est le travail d'un service comme la classe.

[Mise à jour] Je ne pense pas que les modèles de fabrique suggérés soient bons pour cela. Une fabrique est utile si vous voulez créer différentes implémentations de tickets sans mettre cette logique dans le ticket lui-même (via des constructeurs surchargés par exemple).

Examinez le concept de service proposé dans la conception pilotée par le domaine.

Services : Lorsqu'une opération n'appartient conceptuellement à aucun objet. En suivant les contours naturels du problème, on peut implémenter ces opérations dans des services. Le concept de service est appelé "Pure Fabrication" dans GRASP.

4voto

razlebe Points 5181

Le problème, du point de vue de la conception OO, n'est pas tant de savoir si cette fonctionnalité doit être implémentée dans un constructeur (par opposition à d'autres méthodes de cette classe), mais plutôt de savoir si la classe SupportTicket doit savoir comment faire toutes ces choses.

En bref, la classe SupportTicket devrait modéliser un ticket de support, et seulement un ticket de support. Créer un email, savoir comment envoyer cet email au client, mettre le ticket en file d'attente pour traitement, etc. sont autant de fonctionnalités que vous devriez déplacer de votre classe SupportTicket et encapsuler ailleurs. Les avantages de cette démarche sont une réduction du couplage, une plus grande cohésion et une meilleure testabilité.

Jetez un coup d'œil à la Principe de responsabilité unique qui explique les avantages de cette démarche. En particulier, este est un bon endroit pour s'informer sur l'ASR et les autres principes clés d'une bonne conception OO.

2voto

La réponse courte est non.

Dans le domaine de la conception matérielle, nous avions l'habitude de dire : "Ne mettez pas de porte sur l'horloge ou la ligne de réinitialisation - cela obscurcit la logique". La même chose s'applique ici pour la même raison.

La réponse plus longue devra attendre, mais voir " Screetchingly Code évident ".

2voto

user75451 Points 1

En fait, je n'ai jamais été satisfait d'aucune des réponses disponibles, mais examinons-les. Les choix s'articulent autour de deux questions d'évaluation :

E1. Où se trouve la connaissance de la logique d'entreprise ?

E2. Où le prochain lecteur du code regardera-t-il ? ( Code évident et criant )

Quelques choix :

  • Dans le code client (l'objet qui fait "new SupportTicket"). Il ne connaît probablement pas / ne devrait pas connaître la logique commerciale, de toute évidence, sinon vous ne voudriez pas créer ce constructeur fantaisiste. Si c'est le bon endroit pour la logique commerciale, alors il devrait dire :

    $ticket = new SupportTicket($customer, $title);
    
    handleNewSupportTicket($ticket, ...other parameters...)

    où, afin de protéger E2, "handlenewSupportTicket" est l'endroit où cette logique d'entreprise est définie (et où le programmeur suivant peut facilement la trouver).

  • Dans le billet d'assistance comme un appel d'offres distinct. Personnellement, cela ne me convient pas vraiment, car il s'agit de deux appels du code client, alors que la pensée mentale est une seule chose.

    $ticket = new SupportTicket($customer, $title);
    
    $ticket -> handleNewSupportTicket(...other parameters...)
  • Dans le catégorie de tickets d'assistance . Ici, il est prévu que la logique d'entreprise réside dans la zone des tickets d'assistance, mais comme les nouveaux tickets d'assistance doivent absolument être traités immédiatement et non plus tard, cette tâche importante ne peut pas être laissée à l'imagination de quiconque ou à des erreurs de frappe, c'est-à-dire, en particulier, au code client. Je sais seulement comment coder les méthodes de classe en Smalltalk mais je vais essayer de faire un pseudo-code :

    $ticket = SupportTicket.createAndHandleNewSupportTicket(...other parameters...)

    En supposant que le code client ait besoin de l'identifiant du nouveau ticket pour d'autres raisons (sinon le "$ticket =" disparaîtrait).

    Je n'aime pas trop cela, car d'autres programmeurs ne trouvent pas si naturel de chercher la logique métier dans les classes ou les méthodes statiques. Mais c'est le troisième choix.

  • Le seul quatrième choix est s'il y a un autre endroit où la logique d'entreprise réside avec bonheur et où les autres programmeurs vont naturellement la chercher, auquel cas elle est placée dans une classe/fonction statique.

    $ticket = SupportTicketBusinessLogic.createAndHandleNewSupportTicket(...other params...)

    où cette classe/fonction statique effectue les multiples appels nécessaires. (Mais maintenant, nous avons une fois de plus la possibilité que des tickets soient construits et ne soient pas traités correctement :(.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X