2017-07-25 3 views
3

J'écris des classes dans Pharo Smalltalk, mais je suppose que la question est valable pour d'autres implémentations Smalltalk.Attributs obligatoires dans Smalltalk

Je sais qu'un moyen d'imposer des instances avec des attributs spécifiques consiste à fournir une méthode de classe pour la création d'instance, puis à suggérer d'utiliser la méthode de création de classe. Mais tout utilisateur sait que new ou basicNew peut être utilisé à tout moment.

J'ai pensé à invalider new et basicNew en levant une exception, mais cela semble être une mesure trop drastique, ce qui fait que parfois je pourrais avoir besoin de créer des instances pour déboguer par exemple.

Existe-t-il une autre bibliothèque ou un autre mécanisme pour appliquer ces attributs spécifiques à compléter?

Répondre

8

Il n'y en a pas. Et c'est bien.

Voici quelques approches que vous pourriez suivre si:

  1. Assurez-vous que vos objets sont valables en fournissant validateurs eux. C'est un sujet très vaste, donc je dirai simplement qu'un cadre de validation est capable d'examiner vos objets et de rendre explicites toutes les règles qu'ils ne respectent pas. En fonction de ce que vous faites, vous pouvez restreindre les validations à l'interface graphique, lorsque les objets sont nés ou modifiés. Une approche plus sophistiquée, cependant, devrait permettre à n'importe quel objet de décider s'il est valide ou non (dans un contexte donné). Exclure les paramètres individuels du protocole côté instance.

  2. Fournissez seulement plusieurs mots-clés qui échoueront si quelque chose est manquant ou inapproprié. Cela signifie que vous devez fournir des méthodes comme Point >> #x:y: mais pas Poit >> #x: ou Point >> #y:. Comme le suggère l'exemple, il vous sera difficile de suivre cette pratique dans sa totalité car les classes de base ne suivent pas ce style. Notez également que cette pratique nécessitera une sorte de validation car la vérification uniquement pour notNil est généralement trop naïve.

  3. Détendez-vous et ne faites rien tant qu'il n'est pas nécessaire d'aborder ces types de problèmes. En d'autres termes, postez le problème jusqu'à ce que votre logiciel soit assez évolué pour attirer votre attention sur la façon dont ses objets sont créés et modifiés.

La raison pour laquelle je demande les choses sont bien comme ils sont parce que Smalltalk, ayant l'ouverture traditionnellement favorisée par rapport à la sécurité, a facilité et même les gens à expérimenter encouragé, même dans la « mauvaise ». Chaque fonctionnalité visant à empêcher les objets d'être brisés, vous empêchera tôt ou tard de les réparer. Plus important encore, ils vont consommer beaucoup d'énergie qui pourrait autrement être appliquée à des fins plus productives.

+2

Je recommanderais l'option # 3. La seule chose que vous, en tant qu'auteur d'un objet, pourriez (et en fait devrait) faire est d'ajouter votre hypothèse au commentaire de classe. Si vous ajoutez quelque chose comme "Je m'attends à être instancié aveC#newWith: - si vous créez une instance de moi en utilisant #new ou #basicNew, des choses étranges peuvent arriver", alors vous avez fait votre part et tout problème survenant Des instanciations inappropriées/inattendues sont la responsabilité de la personne qui utilise votre objet. Essayer d'anticiper toutes les manières possibles que quelqu'un puisse faire quelque chose de bizarre signifie commencer une bataille que vous perdrez probablement. ;-) –

+0

isValidInContext: anApplicationContext est très utile au niveau de l'application. –

2

Je suis d'accord avec la réponse de Leandro. La programmation défensive est rarement la façon de faire Smalltalk. Il n'y a pas de typage statique pour appliquer quoi que ce soit, ni de messages privés. Smalltalk est ouvert et en retard. Mais d'un autre côté, les erreurs sont rarement catastrophiques (Smalltalk ne plante généralement pas en cas d'erreur).

Cette reliure tardive associée à une récupération d'erreur gracieuse est ce qui rend l'évolution du système très légère. Quand vous programmez dans Smalltalk, vous ne faites que faire évoluer le système live si vous y pensez.Puisque la seule chose que nous avons est d'envoyer des messages, nous pouvons éventuellement l'utiliser pour négocier les contrats, ou simplement vérifier une condition préalable.

La stratégie que vous suggérez est possible à travers l'utilisation d'Exception. L'idée est qu'il est parfois préférable d'avoir une Exception précoce la plus proche possible de la cause première, qu'une Exception tardive plus difficile à déboguer et à corriger. Voir par exemple le message #shouldNotImplement et ses expéditeurs: vous verrez qu'il est parfois utilisé pour empêcher l'utilisation de #new.

N'oubliez pas non plus qu'il existe un message #initialize qui peut être utilisé pour donner une valeur par défaut raisonnable aux variables d'instance (un peu comme le constructeur par défaut de C++). Il existe des variantes lorsque vous souhaitez transmettre des informations supplémentaires: pour les collections qui sont généralement créées via #new:, un message #initialize: prend la taille comme argument. Vous pouvez affiner à volonté des mécanismes similaires pour passer d'autres informations à la création.

Mais n'essayez pas d'empêcher l'utilisation de #basicNew. Vous créeriez de la douleur en interrompant certains services basés sur cette fonctionnalité de bas niveau, comme par exemple la mutation d'instances existantes lorsque vous modifiez la disposition de votre classe, comme la copie, le stockage dans des fichiers externes, etc ... par exemple #adoptInstance:. Au lieu de cela, vous devez apprendre à faire confiance aux utilisateurs de votre bibliothèque (y compris vous-même): les utilisateurs n'utiliseront pas basicNew à moins qu'ils sachent ce qu'ils font (ils apprendront cela tôt ou tard). Et comme l'a dit Amos, documentez les contrats et les attentes dans les commentaires de classe ou de message ou les tests unitaires, afin que les gens puissent apprendre plus rapidement, et peut-être utiliser des noms comme basicNew quand vous voulez exprimer un message.

EDIT en passant, si vous interdisez basicNew, vous ne serez pas en mesure de créer des instances du tout, donc vous devrez créer un nouveau message appelant la primitive pour créer une instance, et à la fin vous Il suffit d'avoir du code obscurci/complexifié pour rien, parce que vous venez de déplacer le problème. À moins que vous faites des choses très désagréables comme:

basicNew 
    thisContext sender method selector == #mySepcialCreationMessageWithParameter: ifTrue: [^super basicNew]. 
    ^self error: 'new instances should be created with mySepcialCreationMessageWithParameter:' 

Comme je l'ai dit plus haut, vous pouvez jouer avec, mais ne le font pas pour de vrai. Un autre point de vue est que le codage est une activité sociale, et le code que vous écrivez dans Smalltalk ne sert pas seulement à programmer un automate. C'est pour être lu et réutilisé par les humains. Dans ce contexte, la programmation défensive est un peu comme la gestion par coercition. Au lieu de perdre du temps à essayer de contraindre, il est plus efficace de passer du temps à créer des abstractions simples et logiques qui peuvent être facilement comprises et réutilisées, peut-être dans d'autres contextes que vous n'aviez pas prévus.