Dans certains de mes codes, j'ai une usine statique similaire à celle-ci:
public class SomeFactory {
// Static class
private SomeFactory() {...}
public static Foo createFoo() {...}
public static Foo createFooerFoo() {...}
}
Lors d'une révision du code, il a été proposé que ce soit un singleton et injecté. Donc, cela devrait ressembler à ceci:
public class SomeFactory {
public SomeFactory() {}
public Foo createFoo() {...}
public Foo createFooerFoo() {...}
}
Quelques points à souligner:
- Les deux usines sont apatrides.
- La seule différence entre les méthodes est leur portée (instance vs statique). Les implémentations sont les mêmes.
- Foo est un bean qui n'a pas d'interface.
Les arguments que j'avais pour devenir statique étaient:
- La classe est sans état, elle n'a donc pas besoin d'être instanciée
- Il semble plus naturel de pouvoir appeler une méthode statique que d'avoir à instancier une fabrique
Les arguments pour l'usine en tant que singleton étaient les suivants:
- C'est bon de tout injecter
- Malgré l'apatridie de l'usine, le test est plus facile avec l'injection (facile à se moquer)
- Il faut se moquer lors du test du consommateur
J'ai de sérieux problèmes avec l'approche singleton car elle semble suggérer qu'aucune méthode ne devrait jamais être statique. Cela semble également suggérer que des utilitaires tels que ceux-ci StringUtils
devraient être enveloppés et injectés, ce qui semble idiot. Enfin, cela implique que je devrai se moquer de l'usine à un moment donné, ce qui ne semble pas correct. Je ne peux pas penser au moment où je devrais me moquer de l'usine.
Que pense la communauté? Bien que je n'aime pas l'approche singleton, je ne semble pas avoir d'argument terriblement fort contre.
DateTime
etFile
sont notoirement difficiles à tester pour exactement les mêmes raisons. Par exemple, si vous avez une classe qui définit laCreated
dateDateTime.Now
dans le constructeur, comment allez-vous créer un test unitaire avec deux de ces objets qui ont été créés à 5 minutes d'intervalle? Et des années à part? Vous ne pouvez vraiment pas le faire (sans beaucoup de travail).private
constructeur et unegetInstance()
méthode? Désolé, incorrigible nit-picker!Réponses:
Pourquoi sépareriez-vous votre usine du type d'objet qu'elle crée?
C'est ce que Joshua Bloch décrit comme son article 1 à la page 5 de son livre, "Java efficace". Java est assez verbeux sans ajouter de classes et singletons supplémentaires et ainsi de suite. Il y a des cas où une classe d'usine séparée a du sens, mais ils sont rares.
Pour paraphraser l'élément 1 de Joshua Bloch, contrairement aux constructeurs, les méthodes d'usine statique peuvent:
Désavantages:
Vraiment, vous devriez apporter le livre de Bloch à votre réviseur de code et voir si vous pouvez tous les deux accepter le style de Bloch.
la source
public static IFoo of(...) { return new Foo(...); }
Quel est le problème? Bloch répertorie la satisfaction de vos préoccupations comme l'un des avantages de cette conception (deuxième puce ci-dessus). Je ne dois pas comprendre votre commentaire.Juste au-dessus de ma tête, voici quelques-uns des problèmes avec la statique en Java:
les méthodes statiques ne jouent pas selon les "règles" de POO. Vous ne pouvez pas forcer une classe à implémenter des méthodes statiques spécifiques (pour autant que je sache). Vous ne pouvez donc pas avoir plusieurs classes implémentant le même ensemble de méthodes statiques avec les mêmes signatures. Donc , vous êtes coincé avec un de quoi que ce soit que vous faites. Vous ne pouvez pas non plus vraiment sous-classer une classe statique. Donc pas de polymorphisme, etc.
puisque les méthodes ne sont pas des objets, les méthodes statiques ne peuvent pas être transmises et elles ne peuvent pas être utilisées (sans faire une tonne de codage standard), sauf par du code qui leur est lié de manière rigide - ce qui rend le code client hautement accouplé. (Pour être juste, ce n'est pas uniquement la faute de la statique).
les champs statiques sont assez similaires aux globaux
Vous avez mentionné:
Ce qui me rend curieux de vous demander:
StringUtils
c'est correctement conçu et mis en œuvre?la source
Foo f = createFoo();
plutôt que d'avoir une instance nommée. L'instance elle-même ne fournit aucun utilitaire. (2) Je pense que oui. Il a été conçu pour surmonter le fait que beaucoup de ces méthodes n'existent pas dans laString
classe. Remplacer quelque chose d'aussi fondamental queString
n'est pas une option, donc les utils sont la voie à suivre. (suite)Integer
. Ils sont très simples, ont très peu de logique et ne sont là que pour vous faciliter la tâche (par exemple, il n'y a pas d'autre raison OO de les avoir). La partie principale de mon argument est qu'ils ne dépendent d'aucun état - il n'y a aucune raison de les isoler pendant les tests. Les gens ne se moquent pasStringUtils
lors des tests - pourquoi auraient-ils besoin de se moquer de cette simple usine de commodité?StringUtils
car il y a une assurance raisonnable que les méthodes là-bas n'ont pas d'effets secondaires.StringUtils
renvoie un résultat sans modifier les entrées, mon usine ne modifie également rien.Je pense que l'application que vous créez doit être assez simple, sinon vous êtes relativement tôt dans son cycle de vie. Le seul autre scénario auquel je peux penser où vous n'auriez pas déjà rencontré de problèmes avec votre statique est si vous avez réussi à limiter les autres parties de votre code qui connaissent votre Factory à un ou deux endroits.
Imaginez que votre application évolue et maintenant, dans certains cas, vous devez produire une
FooBar
(sous-classe de Foo). Maintenant, vous devez parcourir tous les endroits de votre code qui connaissent votreSomeFactory
et écrire une sorte de logique qui regardesomeCondition
et appelleSomeFactory
ouSomeOtherFactory
. En fait, en regardant votre exemple de code, c'est pire que ça. Vous prévoyez d'ajouter simplement méthode après méthode pour créer différents Foos et tout votre code client doit vérifier une condition avant de déterminer quel Foo faire. Ce qui signifie que tous les clients doivent avoir une connaissance intime du fonctionnement de votre usine et qu'ils doivent tous changer chaque fois qu'un nouveau Foo est nécessaire (ou supprimé!).Imaginez maintenant si vous venez de créer un
IFooFactory
qui fait unIFoo
. Maintenant, produire une sous-classe différente ou même une implémentation complètement différente est un jeu d'enfant - vous alimentez simplement la bonne IFooFactory plus haut dans la chaîne, puis lorsque le client l'obtient, il appellegimmeAFoo()
et obtiendra le bon foo parce qu'il a la bonne usine .Vous vous demandez peut-être comment cela se joue dans le code de production. Pour vous donner un exemple de la base de code sur laquelle je travaille qui commence à se stabiliser après un peu plus d'un an de lancement de projets, j'ai un tas d'usines qui sont utilisées pour créer des objets de données Question (ce sont des modèles de présentation ). J'ai un
QuestionFactoryMap
qui est fondamentalement un hachage pour rechercher quelle usine de questions utiliser quand. Donc, pour créer une application qui pose différentes questions, j'ai mis différentes usines sur la carte.Les questions peuvent être présentées dans des instructions ou des exercices (que les deux implémentent
IContentBlock
), de sorte que chacunInstructionsFactory
ouExerciseFactory
obtiendra une copie de QuestionFactoryMap. Ensuite, les différentes usines d'instructions et d'exercices sont remises à celuiContentBlockBuilder
qui conserve à nouveau un hachage à utiliser quand. Et puis, lorsque le XML est chargé, ContentBlockBuilder appelle la fabrique d'exercices ou d'instructions hors du hachage et le lui demandecreateContent()
, puis la fabrique délègue la construction des questions à tout ce qu'elle retire de sa QuestionFactoryMap interne en fonction de ce que est dans chaque nœud XML par rapport au hachage. Malheureusement , cette chose est auBaseQuestion
lieu d'unIQuestion
, mais cela montre que même lorsque vous faites attention à la conception, vous pouvez faire des erreurs qui peuvent vous hanter sur toute la ligne.Votre instinct vous dit que ces statiques vous blesseront, et il y a certainement beaucoup de choses dans la littérature pour soutenir votre intestin. Vous ne perdrez jamais en ayant une injection de dépendances que vous n'utiliserez que pour vos tests unitaires (pas que je pense que si vous construisez un bon code que vous ne vous retrouverez pas à l'utiliser plus que cela), mais la statique peut complètement détruire votre capacité pour modifier rapidement et facilement votre code. Alors pourquoi même y aller?
la source
Integer
classe - des choses simples comme la conversion à partir d'une chaîne. C'estFoo
ça. MonFoo
n'est pas censé être étendu (ma faute de ne pas l'avoir dit), donc je n'ai pas vos problèmes polymorphes. Je comprends la valeur d'avoir des usines polymorphes et les ai utilisées dans le passé ... Je ne pense tout simplement pas qu'elles soient appropriées ici. Pourriez-vous imaginer si vous devez instancier une usine pour effectuer les opérations simplesInteger
qui sont actuellement intégrées via des usines statiques?Foo
, cependant, est beaucoup plus simple que la plupart des classes. C'est juste un simple POJO.if (this) then {makeThisFoo()} else {makeThatFoo()}
tout appeler via votre code. Une chose que j'ai remarquée chez les personnes dont l'architecture est chroniquement mauvaise, c'est qu'elles sont comme des personnes souffrant de douleur chronique. Ils ne savent pas vraiment ce que ça fait de ne pas souffrir, donc la douleur dans laquelle ils se trouvent ne semble pas si grave.Foo
. À juste titre donc - je ne l'ai jamais déclaré définitif.Foo
est un bean qui n'est pas destiné à être étendu.