Conception: méthode Object vs méthode de classe séparée qui prend Object comme paramètre?

14

Par exemple, est-il préférable de faire:

Pdf pdf = new Pdf();
pdf.Print();

ou:

Pdf pdf = new Pdf();
PdfPrinter printer = new PdfPrinter();
printer.Print(pdf);

Un autre exemple:

Country m = new Country("Mexico");
double ratio = m.GetDebtToGDPRatio();

ou:

Country m = new Country("Mexico");
Country us = new Country("US");
DebtStatistics ds = new DebtStatistics();
double usRatio = ds.GetDebtToGDPRatio(us);
double mRatio = ds.GetDebtToGDPRatio(m);    

Dans le dernier exemple, ma préoccupation est qu'il existe des statistiques potentiellement infinies (mais disons même seulement 10) que vous voudrez peut-être connaître d'un pays; appartiennent-ils tous à l'objet pays?

par exemple

Country m = new Country("Mexico");
double ratio = m.GetGDPToMedianIncomeRatio();

Ce sont des ratios simples, mais supposons que les statistiques sont suffisamment compliquées pour justifier une méthode.

Où se situe cette ligne entre les opérations intrinsèques à un objet et les opérations pouvant être effectuées sur un objet mais n'en faisant pas partie?

Utilisateur
la source

Réponses:

16

Prenant vos exemples PDF comme point de départ, regardons cela.

http://en.wikipedia.org/wiki/Single_responsibility_principle

Le principe de responsabilité unique suggère qu'un objet devrait avoir un et un seul objectif. Garde ça en tête.

http://en.wikipedia.org/wiki/Separation_of_concerns

Le principe de séparation des préoccupations nous dit que les classes ne devraient pas avoir de fonctions qui se chevauchent.

Lorsque vous regardez ces deux, ils suggèrent que la logique ne devrait aller dans une classe que si elle a du sens, seulement si cette classe est responsable de le faire.

Maintenant, dans votre exemple PDF, la question est, qui est responsable de l'impression? Qu'est-ce qui a du sens?

Premier extrait de code:

Pdf pdf = new Pdf();
pdf.Print();

Ce n'est pas bien. Un document PDF ne s'imprime pas. Il est imprimé par ... ta da! .. une imprimante. Donc, votre deuxième extrait de code est bien meilleur:

Pdf pdf = new Pdf();
PdfPrinter printer = new PdfPrinter();
printer.Print(pdf);

C'est logique. Une imprimante Pdf imprime un document pdf. Mieux encore, une imprimante ne devrait pas être une imprimante PDF ou une imprimante photo. Il devrait simplement s'agir d'une imprimante capable d'imprimer au mieux de ses capacités.

Pdf pdf = new Pdf();
Printer printer = new Printer();
printer.Print(pdf);

C'est donc simple. Mettez les méthodes là où elles ont du sens. Évidemment, ce n'est pas toujours aussi simple. Prenez par exemple les statistiques de votre pays:

Country m = new Country("Mexico");
double ratio = m.GetDebtToGDPRatio();

Votre préoccupation est qu'il pourrait y avoir n nombre de statistiques, et qu'ils ne devraient pas être dans une classe de pays. C'est vrai. Cependant, si votre modèle n'appelle que ces statistiques particulières, cet exemple de modélisation peut en fait être correct.

Dans ce cas, vous pourriez dire de façon assez logique qu'un pays devrait être en mesure de calculer ses propres statistiques, spécifiques à votre modèle et aux exigences à portée de main.

Et c'est là que réside la chose: quelles sont vos exigences? Vos exigences guideront la façon dont vous modélisez le monde, le contexte dans lequel ces exigences doivent être satisfaites.

Si vous avez en effet une multitude / nombre variable de statistiques, alors votre deuxième exemple a plus de sens:

Country m = new Country("Mexico");
DebtStatistics ds = new DebtStatistics();
double usRatio = ds.GetDebtToGDPRatio(m);

Mieux encore, ayez une superclasse abstraite ou une interface appelée Statistiques qui prend un pays comme paramètre:

interface StatisticsCalculator // or a pure abstract class if doing C++
{
   double getStatistics(Country country); // or a pure virtual function if in C++
}

La classe DebtToGDPRatioStatisticsCalculator implémente StatisticsCalculator ....

classe InfantMortalityStatisticsCalculator implémente StatisticsCalculator ...

Ainsi de suite. Ce qui conduit à ce qui suit: généralisation, délégation, abstraction. La collecte statistique est déléguée à des instances spécifiques qui généralisent une abstraction spécifique (une API de collecte de statistiques).

Je ne sais pas si cela répond à 100% à votre question. Après tout, nous n'avons pas de modèles infaillibles fondés sur des lois inviolables (comme le font les gens d'EE.) Tout ce que vous pouvez faire est de mettre les choses au bon sens. Et c'est une décision d'ingénierie que vous devez prendre. La meilleure chose à faire est de vraiment se familiariser avec les principes OO (et les bons principes de modélisation logicielle en général).

luis.espinal
la source
1
+1 pour l'interface StatisticsCalculator (et l'utilisation ultérieure du modèle de stratégie). Et la réponse approfondie et bien pensée
edwardsmatt
3
temps insuffisant pour déconstruire cela en profondeur pour le moment, mais il faut souligner que la classe d'imprimante deviendra une classe de Dieu au fil du temps, étroitement couplée à toutes sortes de classes de documents. Pdf.Print serait préférable - mais tout dépend de la façon dont vous définissez la «responsabilité unique» ;-)
Steven A. Lowe
@Steve - ce que vous proposez est une horrible idée (avoir Pdf implémente print ()). Il ne reflète pas la façon dont l'impression est mise en œuvre dans la vie réelle. Chaque système d'exploitation et API d'impression que je connais fournit une abstraction Printer. Regardez la liste des imprimantes dans votre machine XP / Vista (ou sous / var / spool ou équivalent dans * nix.) Chaque application sérialise un objet document sur l'une de ses imprimantes. Il n'y a ni imprimante Word, ni imprimante texte ni imprimante PDF. Il existe uniquement des imprimantes spécifiques au périphérique d'impression et non spécifiques au type de document.
luis.espinal
2
+1 J'aime ça Je réfléchis à ce que vous avez dit .. @Steve et luis: Je pense que la pièce manquante du débat sur les objets God est un objet Printer générique devrait accepter quelques formats standard comme ASCII ou bitmap (bien que pdf est probablement raisonnable aussi) et il devrait être de la responsabilité d'une classe de 3e classe de convertir un type de document particulier (par exemple un document MS Word) en l'un de ces formats standard.
Utilisateur
2
Il me semble que peut-être un PDF devrait pouvoir se restituer soit sur une interface Canvas soit dans un objet Image qui pourrait ensuite être traité par un objet Imprimante.
Winston Ewert
4

Je pense que ni l'un ni l'autre n'est définitivement meilleur que l'autre. Utiliser pdf.Print () est plus serré, mais avoir une classe PdfPrinter pourrait être mieux si:

  • Vous devez gérer les instances des imprimantes
  • Il existe un large éventail d'options et d'actions qui pourraient faire exploser la complexité du pdf.Print (...) (par exemple, annulation d'impression, mise en forme supplémentaire, etc.)

Je ne m'y accrocherais pas autrement.

Kevin Hsu
la source
une bonne réponse pratique; le temps nous dira comment cela doit évoluer
Steven A. Lowe
1
La brève suggestion est de considérer à la fois la logique et les données lors de l'application de la SRP, afin de décider si nous regretterons de ne pas les avoir découplés plus tôt. Le problème avec le stockage des paramètres par imprimante dans la Pdfclasse est qu'ils ne sont pas censés être stockés ensemble - Pdfest stocké dans un fichier mais les paramètres par imprimante doivent être stockés avec le profil utilisateur / machine.
rwong