Utilizzare il metodo di costruzione o setter?


16

Sto lavorando a un codice UI in cui ho una Actionclasse, qualcosa del genere -

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

Quando è stata creata questa classe di azioni, si presumeva praticamente che la Actionclasse non fosse personalizzabile (in un certo senso - il suo testo, tooltip o immagine non saranno cambiati da nessuna parte nel codice). Ora, abbiamo bisogno di cambiare il testo dell'azione in qualche posizione nel codice. Quindi, ho suggerito al mio collega di rimuovere il testo dell'azione codificata dal costruttore e di accettarlo come argomento, in modo che tutti siano costretti a passare il testo dell'azione. Qualcosa come questo codice qui sotto -

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

Tuttavia, pensa che poiché il setText()metodo appartiene alla classe base, può essere utilizzato in modo flessibile per passare il testo dell'azione ovunque venga creata l'istanza dell'azione. In questo modo, non è necessario cambiare la MyActionclasse esistente . Quindi il suo codice sarebbe simile a questo.

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

Non sono sicuro che sia un modo corretto di affrontare il problema. Penso che nel caso sopra menzionato l'utente cambierà comunque il testo, quindi perché non forzarlo mentre costruisce l'azione? L'unico vantaggio che vedo con il codice originale è che l'utente può creare la classe di azione senza pensare molto all'impostazione del testo.


1
Il linguaggio che stai usando non consente di sovraccaricare i costruttori?
Mat

1
Sto usando Java, quindi sì, lo permette e penso che potrebbe essere un modo per
gestirlo

2
Vorrei notare che se non hai un modo pubblico per impostare i membri della classe dopo il fatto, la tua classe è effettivamente immutabile . Consentendo a un setter pubblico, la tua classe ora diventa mutevole e potresti doverne tenere conto se dipendevi dall'immutabilità.
cbojar,

Direi che se deve essere impostato affinché il tuo oggetto sia valido, mettilo in ogni costruttore ... se è facoltativo (ha un valore di default ragionevole) e non ti interessa l'immutabilità, mettilo in un setter. Dovrebbe essere impossibile istanziare il tuo oggetto in uno stato non valido o dopo averlo creato in uno stato non valido ove possibile.
Bill K,

Risposte:


15

L'unico vantaggio che vedo con il codice originale è che l'utente può creare la classe di azione senza pensare molto all'impostazione del testo.

Questo non è in realtà un vantaggio, per la maggior parte dei casi è uno svantaggio e nei restanti casi lo definirei un pareggio. Cosa succede se qualcuno dimentica di chiamare setText () dopo la costruzione? E se fosse così in qualche caso insolito, forse un gestore di errori? Se si desidera forzare davvero l'impostazione del testo, è necessario forzarlo al momento della compilazione, poiché solo gli errori in fase di compilazione sono in realtà fatali . Tutto ciò che accade in fase di esecuzione dipende dal particolare percorso del codice in esecuzione.

Vedo due chiari percorsi in avanti:

  1. Usa un parametro costruttore, come suggerisci. Se vuoi davvero, puoi passare nullo una stringa vuota, ma il fatto che non stai assegnando un testo è esplicito piuttosto che implicito. È facile vedere l'esistenza di un nullparametro e vedere che probabilmente è stato inserito qualche pensiero, ma non è così facile vedere la mancanza di una chiamata di metodo e determinare se la mancanza di tale era intenzionale o meno. Per un caso semplice come questo, questo è probabilmente l'approccio che seguirei.
  2. Usa un modello di fabbrica / costruttore. Questo può essere eccessivo per uno scenario così semplice, ma in un caso più generale è altamente flessibile in quanto consente di impostare un numero qualsiasi di parametri e di controllare le condizioni preliminari prima o durante l'istanza dell'oggetto (se la costruzione dell'oggetto è un'operazione di grandi dimensioni e / o la classe può essere utilizzata in più di un modo, questo può essere un enorme vantaggio). Soprattutto in Java è anche un linguaggio comune, e seguire schemi consolidati nel linguaggio e nel framework che stai usando è molto raramente una cosa negativa.

10

Il sovraccarico del costruttore sarebbe una soluzione semplice e diretta qui:

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");
    }
}

È meglio che chiamare .setTextpiù tardi, perché in questo modo nulla deve essere sovrascritto, actionTextpuò essere la cosa prevista fin dall'inizio.

Man mano che il codice si evolve e avrai bisogno di una flessibilità ancora maggiore (cosa che sicuramente accadrà), trarrai vantaggio dal modello di fabbrica / costruttore suggerito da un'altra risposta.


Cosa succede quando vogliono personalizzare una seconda proprietà?
Kevin Cline,

3
Per una proprietà 2nd, 3rd, .., puoi applicare la stessa tecnica, ma più proprietà desideri personalizzare, più diventa ingombrante. Ad un certo punto avrà più senso implementare un modello di fabbrica / costruttore, come ha detto @ michael-kjorling nella sua risposta.
Janos,

6

Aggiungi un metodo 'setText' fluente:

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

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

Cosa potrebbe esserci di più chiaro di così? Se decidi di aggiungere un'altra proprietà personalizzabile, nessun problema.


+1, sono d'accordo, e ho aggiunto un'altra risposta a complemento di più proprietà. Penso che l'API fluente sia più chiara da capire quando hai più di 1 singola proprietà come esempio.
Machado,

Mi piacciono le interfacce fluide, specialmente per i costruttori che producono oggetti immutabili! Più parametri, meglio funziona. Ma guardando l'esempio specifico in questa domanda, suppongo che setText()sia definito sulla classe Action da cui MyAction eredita. Probabilmente ha già un tipo di ritorno vuoto.
GlenPeterson,

1

Proprio come ha detto Kevin Cline nella sua risposta, penso che la strada da percorrere sia creare un'API fluente . Vorrei solo aggiungere che l'API fluente funziona meglio quando si dispone di più di una proprietà che è possibile utilizzare.

Renderà il tuo codice più leggibile e dal mio punto di vista più facile e, aham , "sexy" da scrivere.

Nel tuo caso andrebbe così (scusami per qualsiasi errore di battitura, è passato un anno da quando ho scritto il mio ultimo programma 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;
    }
}

E l'uso sarebbe così:

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

Cattiva idea, se dimenticassero di impostare un testo o qualcosa di importante.
Prakhar,

1

Il consiglio di usare costruttori o costruttori va bene in generale, ma, nella mia esperienza, manca alcuni punti chiave per le azioni, che

  1. Potrebbe essere necessario internazionalizzare
  2. È probabile che il marketing cambi all'ultimo minuto.

Consiglio vivamente che il nome, la descrizione comandi, l'icona ecc ... siano letti da un file delle proprietà, XML, ecc. Ad esempio, per l'azione Apri file, potresti passare una Proprietà e cercare

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

Questo è un formato abbastanza facile da tradurre in francese, per provare una nuova icona migliore, ecc. Senza tempo programmatore o ricompilazione.

Questo è solo uno schema approssimativo, molto è lasciato al lettore ... Cerca altri esempi di internazionalizzazione.


0

È inutile chiamare setText (actionText) o setTooltip ("Il mio suggerimento per gli strumenti di azione") all'interno del costruttore; è più semplice (e ottieni maggiori prestazioni) se semplicemente inizializzi direttamente il campo corrispondente:

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

Se si cambia actionText durante il periodo di vita dell'oggetto corrispondente MyAction, è necessario posizionare un metodo setter; in caso contrario inizializzare il campo solo nel costruttore senza fornire un metodo setter.

Poiché la descrizione comandi e l'immagine sono costanti, trattale come costanti; avere campi:

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

In realtà, quando si progettano oggetti comuni (non bean o oggetti che rappresentano rigorosamente strutture di dati) è una cattiva idea fornire setter e getter, poiché interrompono in qualche modo.


4
Qualsiasi compilatore e JITter competente a metà strada dovrebbe includere le chiamate setText () ecc., Quindi la differenza di prestazione tra una chiamata di funzione per eseguire un compito e avere tale compito al posto della chiamata di funzione, dovrebbe essere al massimo trascurabile e più probabile di non zero.
un CVn il

0

Penso che questo sia vero se stiamo per creare una classe di azione generica (come update, che viene utilizzata per aggiornare Employee, Department ...). Tutto dipende dallo scenario. Se viene creata una classe di azione specifica (come aggiornamento dipendente) (utilizzata in molti luoghi dell'applicazione - Aggiorna dipendente) con l'intenzione di mantenere lo stesso testo, descrizione comandi e immagine in ogni luogo dell'applicazione (per motivi di coerenza). Quindi è possibile eseguire la codifica hardware per testo, descrizione comandi e immagine per fornire testo, descrizione comandi e immagine predefiniti. Ancora per dare maggiore flessibilità, per personalizzare questi, dovrebbe avere i corrispondenti metodi setter. Tenendo presente solo il 10% dei posti, dobbiamo cambiarlo. La risposta al testo dell'azione ogni volta dall'utente può causare un testo diverso ogni volta per la stessa azione. Come "Aggiorna Emp", "Aggiorna dipendente", "Cambia dipendente" o "Modifica dipendente".


Penso che il costruttore sovraccarico dovrebbe ancora risolvere il problema. Perché in tutti i casi "10%", devi prima creare un'azione con il testo predefinito e quindi modificare il testo dell'azione usando il metodo "setText ()". Perché non impostare il testo appropriato durante la costruzione dell'azione.
zswap,

0

Pensa a come verranno utilizzate le istanze e utilizza una soluzione che guiderà o addirittura forzerà gli utenti a utilizzare tali istanze nel modo giusto, o almeno nel migliore dei modi. Un programmatore che utilizza questa classe avrà molte altre cose di cui preoccuparsi e pensare. Questa classe non dovrebbe essere aggiunta all'elenco.

Ad esempio, se si suppone che la classe MyAction sia immutabile dopo la costruzione (e forse altre inizializzazioni), non dovrebbe avere un metodo setter. Se la maggior parte delle volte utilizzerà il "Testo della mia azione" predefinito, dovrebbe esserci un costruttore senza parametri, oltre a un costruttore che consenta un testo opzionale. Ora l'utente non deve pensare di utilizzare la classe correttamente il 90% delle volte. Se l'utente dovrebbe di solito riflettere sul testo, salta il costruttore senza parametri. Ora l'utente è costretto a pensare quando necessario e non può trascurare un passaggio necessario.

Se una MyAction un'istanza deve essere modificabile dopo la costruzione completa, è necessario un setter per il testo. Si è tentati di saltare l'impostazione del valore nel costruttore (principio DRY - "Non ripetere te stesso") e, se il valore predefinito è di solito abbastanza buono lo farei. Ma se non lo è, richiedere il testo nel costruttore costringe l'utente a pensare quando dovrebbe.

Nota che questi utenti non sono stupidi . Hanno solo troppi problemi reali di cui preoccuparsi. Pensando all '"interfaccia" della tua classe, puoi evitare che diventi anche un vero problema - e inutile.


0

Nella seguente soluzione proposta, la superclasse è astratta e ha tutti e tre i membri impostati su un valore predefinito.

La sottoclasse ha diversi costruttori in modo che il programmatore possa istanziarlo.

Se viene utilizzato il primo costruttore, tutti i membri avranno i valori predefiniti.

Se viene utilizzato il secondo costruttore, dai un valore iniziale al membro actionText lasciando altri due membri con il valore predefinito ...

Se si utilizza il terzo costruttore, si crea un'istanza con un nuovo valore per actionText e toolTip, lasciando imageURl con il valore predefinito ...

E così via.

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_);
    }


}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.