Disons que j'ai une procédure qui fait des trucs :
void doStuff(initalParams) {
...
}
Maintenant, je découvre que "faire des choses" est une opération assez complexe. La procédure devient volumineuse, je l'ai divisée en plusieurs procédures plus petites et bientôt je me rends compte qu'avoir une sorte d' état serait utile tout en faisant des trucs, de sorte que j'ai besoin de passer moins de paramètres entre les petites procédures. Donc, je le factorise dans sa propre classe:
class StuffDoer {
private someInternalState;
public Start(initalParams) {
...
}
// some private helper procedures here
...
}
Et puis je l'appelle comme ça:
new StuffDoer().Start(initialParams);
ou comme ça:
new StuffDoer(initialParams).Start();
Et c'est ce qui ne va pas. Lorsque j'utilise l'API .NET ou Java, je n'appelle jamais new SomeApiClass().Start(...);
, ce qui me fait penser que je me trompe. Bien sûr, je pourrais rendre le constructeur de StuffDoer privé et ajouter une méthode d'assistance statique:
public static DoStuff(initalParams) {
new StuffDoer().Start(initialParams);
}
Mais alors j'aurais une classe dont l'interface externe se compose d'une seule méthode statique, ce qui semble également bizarre.
D'où ma question: existe-t-il un modèle bien établi pour ce type de classes qui
- avoir un seul point d'entrée et
- n'ont pas d'état "reconnaissable en externe", c'est-à-dire que l'état d'instance n'est requis que pendant l' exécution de ce point d'entrée?
bool arrayContainsSomestring = new List<string>(stringArray).Contains("somestring");
quand tout ce qui m'intéressait était cette information particulière et les méthodes d'extension LINQ ne sont pas disponibles. Fonctionne bien et s'adapte à l'intérieur d'uneif()
condition sans avoir besoin de sauter à travers des cerceaux. Bien sûr, vous voulez un langage récupéré si vous écrivez du code comme ça.Réponses:
Il existe un modèle appelé objet de méthode dans lequel vous factorisez une seule grande méthode avec beaucoup de variables / arguments temporaires dans une classe distincte. Vous faites cela plutôt que d'extraire simplement des parties de la méthode dans des méthodes distinctes car elles ont besoin d'accéder à l'état local (les paramètres et les variables temporaires) et vous ne pouvez pas partager l'état local à l'aide de variables d'instance car elles seraient locales à cette méthode (et les méthodes qui en sont extraites) uniquement et ne seraient pas utilisées par le reste de l'objet.
Ainsi, à la place, la méthode devient sa propre classe, les paramètres et les variables temporaires deviennent des variables d'instance de cette nouvelle classe, puis la méthode est divisée en méthodes plus petites de la nouvelle classe. La classe résultante a généralement une seule méthode d'instance publique qui exécute la tâche que la classe encapsule.
la source
Je dirais que la classe en question (utilisant un seul point d'entrée) est bien conçue. Il est facile à utiliser et à étendre. Assez SOLIDE.
Même chose pour
no "externally recognizable" state
. C'est une bonne encapsulation.Je ne vois aucun problème avec du code comme:
la source
doer
, cela se réduit àvar result = new StuffDoer(initialParams).Calculate(extraParams);
.StringComparer
classe que vous utilisez est-elle aussinew StringComparer().Compare( "bleh", "blah" )
bien conçue? Qu'en est-il de la création d'un État alors qu'il n'y en a absolument pas besoin? D'accord, le comportement est bien encapsulé (enfin, au moins pour l'utilisateur de la classe, plus à ce sujet dans ma réponse ), mais cela ne le rend pas «bon design» par défaut. Quant à votre exemple de «résultat de l'action». Cela n'aurait de sens que si vous utilisiez un élémentdoer
distinct deresult
.one reason to change
. La différence peut sembler subtile. Vous devez comprendre ce que cela signifie. Il ne créera jamais une seule classe de méthodeDu point de vue des principes SOLIDES , la réponse de Jgauffin est logique. Cependant, vous ne devez pas oublier les principes généraux de conception, par exemple le masquage d'informations .
Je vois plusieurs problèmes avec l'approche donnée:
Quelques références liées
Peut - être un point essentiel de l' argumentation réside dans la façon dont la mesure d'étirer la responsabilité unique principe . "Si vous allez à l'extrême et que vous construisez des classes qui ont une raison d'exister, vous pouvez vous retrouver avec une seule méthode par classe. Cela entraînerait une grande prolifération de classes pour les processus les plus simples, ce qui entraînerait difficile à comprendre et difficile à changer. "
Une autre référence pertinente concernant ce sujet: "Divisez vos programmes en méthodes qui effectuent une tâche identifiable. Conservez toutes les opérations d'une méthode au même niveau d'abstraction ." - Kent Beck Key est ici "le même niveau d'abstraction". Cela ne signifie pas «une chose», comme cela est souvent interprété. Ce niveau d'abstraction dépend entièrement du contexte pour lequel vous concevez.
Quelle est donc la bonne approche?
Sans connaître votre cas concret d'utilisation, c'est difficile à dire. Il y a un scénario dans lequel j'utilise parfois (pas souvent) une approche similaire. Lorsque je souhaite traiter un ensemble de données, sans vouloir rendre cette fonctionnalité disponible pour l'ensemble de la portée de la classe. J'ai écrit un blog à ce sujet, comment les lambdas peuvent encore améliorer l'encapsulation . J'ai également commencé une question sur le sujet ici sur les programmeurs . Ce qui suit est un dernier exemple où j'ai utilisé cette technique.
Étant donné que le code très «local» dans le
ForEach
n'est réutilisé nulle part ailleurs, je peux simplement le conserver à l'emplacement exact où il est pertinent. Décrire le code de telle manière que le code qui dépend les uns des autres est fortement regroupé le rend plus lisible à mon avis.Alternatives possibles
la source
Je dirais que c'est assez bon, mais que votre API devrait toujours être une méthode statique sur une classe statique, car c'est ce que l'utilisateur attend. Le fait que la méthode statique utilise
new
pour créer votre objet d'assistance et effectuer le travail est un détail d'implémentation qui doit être caché à celui qui l'appelle.la source
Il y a un problème avec les développeurs désemparés qui pourraient utiliser une instance partagée et l'utiliser
Cela nécessite donc une documentation explicite selon laquelle il n'est pas sûr pour les threads et s'il est léger (sa création est rapide, ne bloque pas les ressources externes ni beaucoup de mémoire) qu'il est OK d'instancier à la volée.
Le masquer dans une méthode statique aide à cela, car la réutilisation de l'instance ne se produit jamais.
S'il a une initialisation coûteuse, il pourrait (ce n'est pas toujours) avantageux de préparer l'initialisation une fois et de l'utiliser plusieurs fois en créant une autre classe qui contiendrait uniquement l'état et la rendrait clonable. L'initialisation créerait un état dans lequel serait stocké
doer
.start()
le clonerait et le transmettrait à des méthodes internes.Cela permettrait également d'autres choses comme l'état persistant d'exécution partielle. (Si cela prend du temps et que des facteurs externes, par exemple une panne de courant, peuvent interrompre l'exécution.) Mais ces éléments fantaisistes supplémentaires ne sont généralement pas nécessaires, donc cela n'en vaut pas la peine.
la source
Outre mon autre réponse plus élaborée et personnalisée , je pense que l'observation suivante mérite une réponse distincte.
Il y a des indications que vous suivez peut-être l'anti-modèle de Poltergeists .
la source
Il existe plusieurs modèles sur le catalogue P de EAA de Martin Fowler ;
la source