Utiliser une méthode constructeur ou setter?


16

Je travaille sur un code d'interface utilisateur où j'ai une Actionclasse, quelque chose comme ça -

public class MyAction extends Action {
    public MyAction() {
        setText("My Action Text");
        setToolTip("My Action Tool tip");
        setImage("Some Image");
    }
}

Lorsque cette classe Action a été créée, il était à peu près supposé que la Actionclasse ne serait pas personnalisable (dans un sens - son texte, son info-bulle ou son image ne seront modifiés nulle part dans le code). Maintenant, nous avons besoin de changer le texte de l'action à un certain endroit dans le code. J'ai donc suggéré à mon collègue de supprimer le texte d'action codé en dur du constructeur et de l'accepter comme argument, afin que tout le monde soit obligé de passer le texte d'action. Quelque chose comme ce code ci-dessous -

public class MyAction extends Action {
    public MyAction(String actionText) {
        setText(actionText);
        setTooltip("My Action tool tip"); 
        setImage("My Image"); 
    }
}

Cependant, il pense que puisque la setText()méthode appartient à la classe de base, elle peut être utilisée de manière flexible pour passer le texte d'action partout où l'instance d'action est créée. De cette façon, il n'est pas nécessaire de modifier la MyActionclasse existante . Donc, son code ressemblerait à quelque chose comme ça.

MyAction action = new MyAction(); //this creates action instance with the hardcoded text
action.setText("User required new action text"); //overwrite the existing text.

Je ne sais pas si c'est une bonne façon de régler le problème. Je pense que dans le cas mentionné ci-dessus, l'utilisateur va de toute façon changer le texte, alors pourquoi ne pas le forcer lors de la construction de l'action? Le seul avantage que je vois avec le code d'origine est que l'utilisateur peut créer une classe Action sans trop penser à définir du texte.


1
Le langage que vous utilisez ne permet pas de surcharger les constructeurs?
Mat

1
J'utilise Java, alors oui, cela le permet et je pense que cela pourrait être une façon de résoudre ce
problème

2
Je voudrais noter que si vous n'avez aucun moyen public de définir les membres de la classe après coup, votre classe est effectivement immuable . En autorisant un setter public, votre classe devient désormais modifiable , et vous devrez peut-être en tenir compte si vous dépendez de l'immuabilité.
cbojar

Je dirais que s'il doit être défini pour que votre objet soit valide, mettez-le dans chaque constructeur ... s'il est facultatif (a un défaut raisonnable) et que vous ne vous souciez pas de l'immuabilité, mettez-le dans un setter. Il devrait être impossible d'instancier votre objet dans un état invalide ou après l'instanciation de le mettre dans un état invalide dans la mesure du possible.
Bill K

Réponses:


15

Le seul avantage que je vois avec le code d'origine est que l'utilisateur peut créer une classe Action sans trop penser à définir du texte.

Ce n'est en fait pas un avantage, dans la plupart des cas, c'est un inconvénient et dans les autres cas, j'appellerais cela une égalité. Et si quelqu'un oublie d'appeler setText () après la construction? Et si tel est le cas dans un cas inhabituel, peut-être un gestionnaire d'erreurs? Si vous voulez vraiment forcer le texte à être défini, vous devez le forcer au moment de la compilation, car seules les erreurs au moment de la compilation sont réellement fatales . Tout ce qui se produit au moment de l'exécution dépend de l'exécution de ce chemin de code particulier.

Je vois deux voies claires en avant:

  1. Utilisez un paramètre constructeur, comme vous le suggérez. Si vous le voulez vraiment, vous pouvez passer nullou une chaîne vide, mais le fait que vous n'affectez pas de texte est explicite plutôt qu'implicite. Il est facile de voir l'existence d'un nullparamètre et de voir qu'il y avait probablement une réflexion, mais pas si facile de voir l'absence d'un appel de méthode et de déterminer si l'absence d'un tel était intentionnel ou non. Pour un cas simple comme celui-ci, c'est probablement l'approche que j'adopterais.
  2. Utilisez un modèle d'usine / constructeur. Cela peut être exagéré pour un scénario aussi simple, mais dans un cas plus général, il est très flexible car il vous permet de définir n'importe quel nombre de paramètres et de vérifier les conditions préalables avant ou pendant l'instanciation de l'objet (si la construction de l'objet est une opération de grande envergure et / ou la classe peut être utilisée de plusieurs façons, cela peut être un énorme avantage). Particulièrement en Java, c'est aussi un idiome commun, et suivre des modèles établis dans le langage et le framework que vous utilisez est très rarement une mauvaise chose.

10

La surcharge du constructeur serait une solution simple et directe ici:

public class MyAction extends Action {
    public MyAction(String actionText) {
        setText(actionText);
        setTooltip("My Action tool tip"); 
        setImage("My Image"); 
    }
    public MyAction() {
        this("My Action Text");
    }
}

Il vaut mieux que d'appeler .setTextplus tard, car de cette façon, rien n'a besoin d'être écrasé, actionTextpeut être la chose prévue dès le début.

Au fur et à mesure que votre code évolue et que vous aurez besoin de plus de flexibilité (ce qui arrivera sûrement), vous bénéficierez du modèle d'usine / constructeur suggéré par une autre réponse.


Que se passe-t-il lorsqu'ils souhaitent personnaliser une deuxième propriété?
kevin cline

3
Pour une propriété 2nd, 3rd, .., vous pouvez appliquer la même technique, mais plus vous souhaitez personnaliser de propriétés, plus cela devient compliqué. À un moment donné, il sera plus logique de mettre en œuvre un modèle d'usine / constructeur, comme l'a dit @ michael-kjorling dans sa réponse.
janos

6

Ajoutez une méthode courante 'setText':

public class MyAction ... {
  ...
  public MyAction setText(String text) { ... ; return this; }
}

MyAction a = new MyAction().setText("xxx");

Quoi de plus clair que ça? Si vous décidez d'ajouter une autre propriété personnalisable, pas de problème.


+1, je suis d'accord, et a ajouté une autre réponse complétant avec plus de propriétés. Je pense que l'API courante est plus claire à comprendre lorsque vous avez plus d'une propriété unique comme exemple.
Machado

J'aime les interfaces fluides, surtout pour les constructeurs qui produisent des objets immuables! Plus il y a de paramètres, mieux cela fonctionne. Mais en regardant l'exemple spécifique dans cette question, je suppose que cela setText()est défini sur la classe Action dont MyAction hérite. Il a probablement déjà un type de retour vide.
GlenPeterson

1

Tout comme l'a dit Kevin Cline dans sa réponse, je pense que la voie à suivre est de créer une API fluide . Je voudrais simplement ajouter que l'API courante fonctionne mieux lorsque vous avez plusieurs propriétés que vous pouvez utiliser.

Cela rendra votre code plus lisible, et de mon point de vue plus facile et, aham , "sexy" à écrire.

Dans votre cas, cela se passerait comme ça (désolé pour toute faute de frappe, cela fait un an que j'ai écrit mon dernier programme java):

 public class MyAction extends Action {
    private String _text     = "";
    private String _tooltip  = "";
    private String _imageUrl = "";

    public MyAction()
    {
       // nothing to do here.
    }

    public MyAction text(string value)
    {
       this._text = value;
       return this;
    }

    public MyAction tooltip(string value)
    {
       this._tooltip = value;
       return this;
    }

    public MyAction image(string value)
    {
       this._imageUrl = value;
       return this;
    }
}

Et l'utilisation serait comme ceci:

MyAction action = new MyAction()
    .text("My Action Text")
    .tooltip("My Action Tool tip")
    .image("Some Image");

Mauvaise idée, et s'ils oublient de mettre du texte ou quelque chose d'important.
Prakhar

1

Le conseil d'utiliser des constructeurs ou des constructeurs est très bien en général, mais, selon mon expérience, il manque certains points clés pour les actions, qui

  1. Peut-être besoin d'être internationalisé
  2. Il est probable que le marketing change à la dernière minute.

Je suggère fortement que le nom, l'info-bulle, l'icône etc ... soit lu à partir d'un fichier de propriétés, XML, etc. Par exemple, pour l'action d'ouverture de fichier, vous pouvez passer une propriété et rechercher

File.open.name=Open
File.open.tooltip=Open a file
File.open.icon=somedir/open.jpg

Il s'agit d'un format assez facile à traduire en français, pour essayer une nouvelle meilleure icône, etc. Sans temps de programmation ni recompilation.

Ceci est juste un aperçu, beaucoup est laissé au lecteur ... Cherchez d'autres exemples d'internationalisation.


0

Il est inutile d'appeler setText (actionText) ou setTooltip ("My tool tool tip") à l'intérieur du constructeur; c'est plus facile (et vous gagnez en performances) si vous initialisez simplement le champ correspondant directement:

    public MyAction(String actionText) {
        this.actionText = actionText;
    }

Si vous modifiez actionText pendant la durée de vie de l'objet correspondant MyAction, vous devez placer une méthode de définition; sinon, initialisez le champ uniquement dans le constructeur sans fournir de méthode de définition.

Comme l'infobulle et l'image sont des constantes, traitez-les comme des constantes; avoir des champs:

private (or even public) final static String TOOLTIP = "My Action Tooltip";

En fait, lors de la conception d'objets communs (pas des beans ou des objets représentant strictement des structures de données), c'est une mauvaise idée de fournir des setters et des getters, car ils cassent en quelque sorte l'encapsulation.


4
Tout compilateur compétent à mi-chemin et JITter doivent aligner les appels setText (), etc. que non nul.
un CVn le

0

Je pense que cela est vrai si nous allons créer une classe d'actions générique (comme update, qui est utilisée pour mettre à jour Employee, Department ...). Tout dépend du scénario. Si une classe d'action spécifique (comme mettre à jour l'employé) (utilisée à plusieurs endroits dans l'application - Mettre à jour l'employé) est créée avec l'intention de conserver le même texte, info-bulle et image à chaque endroit de l'application (pour un point de vue de cohérence). Le codage en dur peut donc être effectué pour le texte, l'infobulle et l'image pour fournir le texte, l'infobulle et l'image par défaut. Toujours pour donner plus de flexibilité, pour les personnaliser, il devrait avoir des méthodes de définition correspondantes. En gardant à l'esprit seulement 10% des endroits, nous devons le changer. La prise de texte d'action à chaque fois de la part de l'utilisateur peut entraîner un texte différent à chaque fois pour la même action. Comme «Mettre à jour l'Emp», «Mettre à jour l'employé», «Changer d'employé» ou «Modifier l'employé».


Je pense que le constructeur surchargé devrait toujours résoudre le problème. Parce que dans tous les cas "10%", vous allez d'abord créer une Action avec le texte par défaut puis changer le texte de l'action en utilisant la méthode "setText ()". Pourquoi ne pas définir le texte approprié lors de la construction de l'action.
zswap

0

Pensez à la façon dont les instances seront utilisées et utilisez une solution qui guidera, voire forcera, les utilisateurs à utiliser ces instances de la bonne manière, ou du moins de la meilleure façon. Un programmeur utilisant cette classe devra s'inquiéter et penser à beaucoup d'autres choses. Cette classe ne doit pas s'ajouter à la liste.

Par exemple, si la classe MyAction est censée être immuable après la construction (et éventuellement une autre initialisation), elle ne devrait pas avoir de méthode de définition. Si la plupart du temps, il utilisera le "Mon texte d'action" par défaut, il devrait y avoir un constructeur sans paramètre, plus un constructeur qui autorise un texte optionnel. Maintenant, l'utilisateur n'a plus besoin de penser à utiliser correctement la classe dans 90% des cas. Si l'utilisateur doit généralement réfléchir au texte, ignorez le constructeur sans paramètre. Maintenant, l'utilisateur est obligé de penser quand cela est nécessaire et ne peut ignorer une étape nécessaire.

Si un MyAction instance doit être modifiable après la construction complète, vous avez besoin d'un setter pour le texte. Il est tentant de sauter la définition de la valeur dans le constructeur (principe DRY - "Don't Repeat Yourself") et, si la valeur par défaut est généralement assez bonne, je le ferais. Mais si ce n'est pas le cas, exiger le texte dans le constructeur oblige l'utilisateur à penser quand il le devrait.

Notez que ces utilisateurs ne sont pas stupides . Ils ont juste trop de problèmes réels à craindre. En pensant à l '"interface" de votre classe, vous pouvez l'empêcher de devenir un vrai problème aussi - et inutile.


0

Dans la solution proposée suivante, la superclasse est abstraite et a les trois membres définis sur une valeur par défaut.

La sous-classe a différents constructeurs pour que le programmeur puisse l'instancier.

Si le premier constructeur est utilisé, tous les membres auront les valeurs par défaut.

Si un deuxième constructeur est utilisé, vous donnez une valeur initiale au membre actionText laissant aux deux autres membres la valeur par défaut ...

Si un troisième constructeur est utilisé, vous l'instanciez avec une nouvelle valeur pour actionText et toolTip, en laissant imageURl avec la valeur par défaut ...

Etc.

public abstract class Action {
    protected String text = "Default action text";
    protected String toolTip = "Default action tool tip";
    protected String imageURl = "http://myserver.com/images/default.png";

    .... rest of code, I guess setters and getters
}

public class MyAction extends Action {


    public MyAction() {

    }

    public MyAction(String actionText) {
        setText(actionText);
    }

    public MyAction(String actionText, String toolTip_) {
        setText(actionText);
        setToolTip(toolTip_);   
    }

    public MyAction(String actionText, String toolTip_; String imageURL_) {
        setText(actionText);
        setToolTip(toolTip_);
        setImageURL(imageURL_);
    }


}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.