J'ai une classe qui met en place un tableau de nœuds et les connecte les uns aux autres dans une structure de type graphique. Est-il préférable de:
- Conservez la fonctionnalité pour initialiser et connecter les nœuds dans une seule fonction
- Avoir la fonctionnalité d'initialisation et de connexion dans deux fonctions différentes (et avoir un ordre dépendant sur lequel les fonctions doivent être appelées - mais gardez à l'esprit que ces fonctions sont privées.)
Méthode 1: (mauvaise dans la mesure où une fonction fait deux choses, MAIS elle conserve les fonctionnalités dépendantes regroupées - les nœuds ne doivent jamais être connectés sans avoir été initialisés au préalable.)
init() {
setupNodes()
}
private func setupNodes() {
// 1. Create array of nodes
// 2. Go through array, connecting each node to its neighbors
// according to some predefined constants
}
Méthode 2: (Mieux dans le sens où il est auto-documenté, MAIS connectNodes () ne doit jamais être appelé avant setupNodes (), donc toute personne travaillant avec les internes de la classe doit connaître cet ordre.)
init() {
setupNodes()
}
private func setupNodes() {
createNodes()
connectNodes()
}
private func createNodes() {
// 1. Create array of nodes
}
private func connectNodes() {
// 2. Go through array, connecting each node to its neighbors
// according to some predefined constants
}
Excité d'entendre des pensées.
Réponses:
Le problème que vous traitez est appelé couplage temporel
Vous avez raison de vous inquiéter de la compréhension de ce code:
Je peux deviner ce qui se passe là-bas, mais dites-moi si cela rend les choses un peu plus claires:
Cela a l'avantage supplémentaire d'être moins couplé à la modification des variables d'instance, mais pour moi, être lisible est le numéro un.
Cela rend
connectNodes()
explicite la dépendance de nœuds.la source
Fonctions distinctes , pour deux raisons:
1. Les fonctions privées sont privées pour exactement cette situation.Votre
init
fonction est publique, et c'est son interface, son comportement et sa valeur de retour que vous devez vous soucier de protéger et de modifier. Le résultat que vous attendez de cette méthode sera le même quelle que soit l'implémentation que vous utilisez.Puisque le reste de la fonctionnalité est caché derrière ce mot-clé privé, il peut être implémenté comme vous le souhaitez ... vous pouvez donc aussi le rendre agréable et modulaire, même si un bit dépend de l'autre appelé en premier.
2. La connexion de nœuds entre eux peut ne pas être une fonction privéeQue se passe-t-il si à un moment donné vous souhaitez ajouter d'autres nœuds à la baie? Détruisez-vous la configuration que vous avez maintenant et réinitialisez-la complètement? Ou ajoutez-vous des nœuds à la baie existante, puis réexécutez-vous
connectNodes
?Peut éventuellement
connectNodes
avoir une réponse sensée si le tableau de nœuds n'a pas encore été créé (lever une exception? Retourner un ensemble vide? Vous devez décider ce qui a du sens pour votre situation).la source
Vous pouvez également trouver (selon la complexité de chacune de ces tâches) que c'est une bonne couture pour diviser une autre classe.
(Je ne sais pas si Swift fonctionne de cette façon mais un pseudo-code :)
Cela sépare les responsabilités de la création et de la modification des nœuds en classes distinctes:
NodeGenerator
ne se soucie que de créer / récupérer des nœuds, tandisYourClass
que se soucie uniquement de connecter les nœuds qui lui sont donnés.la source
En plus d'être le but exact des méthodes privées, Swift vous donne la possibilité d'utiliser des fonctions internes.
Les méthodes internes sont parfaites pour les fonctions qui n'ont qu'un seul site d'appel, mais ont l'impression qu'elles ne justifient pas d'être des fonctions privées distinctes.
Par exemple, il est très courant d'avoir une fonction "entrée" récursive publique, qui vérifie les conditions préalables, configure certains paramètres et délègue à une fonction récursive privée qui fait le travail.
Voici un exemple de ce à quoi cela pourrait ressembler dans ce cas:
Faites attention à la façon dont j'utilise les valeurs de retour et les paramètres pour faire circuler les données, plutôt que de muter un état partagé. Cela rend le flux de données beaucoup plus évident à première vue, sans avoir à sauter dans l'implémentation.
la source
Chaque fonction que vous déclarez entraîne le fardeau d'ajouter de la documentation et de la généraliser afin qu'elle soit utilisable par d'autres parties du programme. Il porte également le fardeau de comprendre comment d'autres fonctions du fichier peuvent l'utiliser pour quelqu'un qui lit le code.
Si toutefois il n'est pas utilisé par d'autres parties de votre programme, je ne l'exposerais pas comme une fonction distincte.
Si votre langue le prend en charge, vous pouvez toujours avoir une fonction-une-chose en utilisant des fonctions imbriquées
Le lieu de la déclaration est très important, et dans l'exemple ci-dessus, il est clair sans avoir besoin d'indices supplémentaires que les fonctions internes sont destinées à être utilisées uniquement dans le corps de la fonction externe.
Même si vous les déclarez en tant que fonctions privées, je suppose qu'elles sont toujours visibles pour l'ensemble du fichier. Vous devez donc les déclarer proches de la déclaration de la fonction principale et ajouter de la documentation qui précise qu'ils ne doivent être utilisés que par la fonction externe.
Je ne pense pas que vous ayez à faire strictement l'un ou l'autre. La meilleure chose à faire varie au cas par cas.
Le décomposer en plusieurs fonctions ajoute certainement une surcharge pour comprendre pourquoi il y a 3 fonctions et comment elles fonctionnent toutes les unes avec les autres, mais si la logique est complexe, cette surcharge supplémentaire peut être bien inférieure à la simplicité introduite par la décomposition de la logique complexe. en parties plus simples.
la source
private
autorise l'accès uniquement dans le type englobant (struct / class / enum), tandis qu'ilfileprivate
autorise l'accès à l'ensemble du fichier