Use o método construtor ou setter?

16

Estou trabalhando em um código de interface do usuário em que tenho uma Actionclasse, algo como isto -

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

Quando essa classe Action foi criada, foi assumido que a Actionclasse não seria personalizável (em certo sentido - seu texto, dica de ferramenta ou imagem não serão alterados em nenhum lugar do código). Agora, precisamos alterar o texto da ação em algum local do código. Então, sugeri ao meu colega de trabalho para remover o texto da ação codificado do construtor e aceitá-lo como argumento, para que todos sejam forçados a passar o texto da ação. Algo como este código abaixo -

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

Ele, no entanto, pensa que, como o setText()método pertence à classe base, pode ser usado com flexibilidade para passar o texto da ação onde quer que a instância da ação seja criada. Dessa forma, não há necessidade de alterar a MyActionclasse existente . Portanto, seu código seria algo parecido com isto.

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

Não tenho certeza se essa é uma maneira correta de lidar com o problema. Eu acho que, no caso acima mencionado, o usuário alterará o texto, então por que não forçá-lo durante a construção da ação? O único benefício que vejo no código original é que o usuário pode criar a classe Action sem pensar muito em definir o texto.

zswap
fonte
1
O idioma que você está usando não permite sobrecarregar construtores?
Mat
1
Eu estou usando Java.So Sim, ele não permite e eu acho que poderia ser uma maneira de lidar com isso
zswap
2
Eu observaria que, se você não tiver um meio público de definir os alunos após o fato, sua classe será efetivamente imutável . Ao permitir um setter público, sua classe agora se torna mutável e pode ser necessário levar isso em consideração se você depender da imutabilidade.
Cbojar
Eu diria que, se ele precisar ser definido para que seu objeto seja válido, coloque-o em todos os construtores ... se for opcional (tem um padrão razoável) e você não se importa com imutabilidade, coloque-o em um setter. Deve ser impossível instanciar seu objeto em um estado inválido ou após instanciação colocá-lo em um estado inválido sempre que possível.
Bill K

Respostas:

15

O único benefício que vejo no código original é que o usuário pode criar a classe Action sem pensar muito em definir o texto.

Na verdade, isso não é um benefício, para a maioria dos casos, é uma desvantagem e, nos demais casos, eu chamaria de empate. E se alguém esquecer de chamar setText () após a construção? E se esse for o caso em algum caso incomum, talvez um manipulador de erros? Se você realmente deseja forçar o texto a ser definido, é necessário forçá-lo no momento da compilação, pois apenas erros em tempo de compilação são realmente fatais . Tudo o que acontece no tempo de execução depende do caminho de código específico que está sendo executado.

Eu vejo dois caminhos claros adiante:

  1. Use um parâmetro construtor, como você sugere. Se você realmente deseja, pode passar nullou uma sequência vazia, mas o fato de não estar atribuindo um texto é explícito e não implícito. É fácil ver a existência de um nullparâmetro e ver que provavelmente houve algum pensamento nele, mas não é tão fácil ver a falta de uma chamada de método e determinar se a falta foi intencional ou não. Para um caso simples como esse, essa é provavelmente a abordagem que eu adotaria.
  2. Use um padrão de fábrica / construtor. Isso pode ser um exagero para um cenário tão simples, mas, em um caso mais geral, é altamente flexível, pois permite definir qualquer número de parâmetros e verificar as condições prévias antes ou durante a instanciação do objeto (se a construção do objeto for uma operação grande e / ou a classe pode ser usada de mais de uma maneira, isso pode ser uma grande vantagem). Particularmente em Java, também é um idioma comum, e seguir padrões estabelecidos na linguagem e na estrutura que você está usando raramente é algo ruim.
um CVn
fonte
10

A sobrecarga de construtores seria uma solução simples e direta aqui:

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

É melhor do que ligar .setTextmais tarde, porque dessa forma nada precisa ser substituído, actionTextpode ser a coisa pretendida desde o início.

À medida que seu código evolui e você precisará de ainda mais flexibilidade (o que certamente acontecerá), você se beneficiará do padrão de fábrica / construtor sugerido por outra resposta.

janos
fonte
O que acontece quando eles desejam personalizar uma segunda propriedade?
kevin Cline
3
Para uma segunda, terceira, .. propriedade, você pode aplicar a mesma técnica, mas quanto mais propriedades você desejar personalizar, mais difícil será a operação. Em algum momento, fará mais sentido implementar um padrão de fábrica / construtor, como disse @ michael-kjorling em sua resposta.
janos
6

Adicione um método fluente 'setText':

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

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

O que poderia ser mais claro do que isso? Se você decidir adicionar outra propriedade personalizável, não há problema.

Kevin Cline
fonte
+1, concordo e adicionei outra resposta que complementa com mais propriedades. Eu acho que a API fluente é mais clara de entender quando você tem mais de uma propriedade única como exemplo.
Machado
Eu gosto de interfaces fluentes, especialmente para construtores que produzem objetos imutáveis! Quanto mais parâmetros, melhor isso funciona. Mas, olhando o exemplo específico nesta pergunta, acho que setText()é definido na classe Action da qual MyAction herda. Provavelmente já possui um tipo de retorno nulo.
GlenPeterson
1

Assim como Kevin Cline disse em sua resposta, acho que o caminho a seguir é criar uma API fluente . Gostaria apenas de acrescentar que a API fluente funciona melhor quando você possui mais de uma propriedade.

Isso tornará seu código mais legível e, do meu ponto de vista, mais fácil e, aham , "sexy" de escrever.

No seu caso, seria assim (desculpe-me por qualquer erro de digitação, já faz um ano desde que escrevi meu último programa 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 o uso seria assim:

MyAction action = new MyAction()
    .text("My Action Text")
    .tooltip("My Action Tool tip")
    .image("Some Image");
Machado
fonte
Má ideia, e se eles esquecerem de definir texto ou qualquer coisa importante.
Prakhar
1

O conselho para usar construtores ou construtores é bom em geral, mas, na minha experiência, faltam alguns pontos-chave para Ações, que

  1. Possivelmente precisa ser internacionalizado
  2. É provável que o marketing mude no último minuto.

Eu sugiro fortemente que o nome, a dica de ferramenta, o ícone etc ... sejam lidos em um arquivo de propriedades, XML, etc. Por exemplo, para a ação Abrir arquivo, você poderia passar uma propriedade e ela procuraria

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

Este é um formato muito fácil de traduzir para o francês, para tentar um novo ícone melhor, etc. Sem tempo de programador ou recompilação.

Este é apenas um esboço, muito resta ao leitor ... Procure outros exemplos de internacionalização.

user949300
fonte
0

É inútil chamar setText (actionText) ou setTooltip ("Minha dica da ferramenta de ação") dentro do construtor; é mais fácil (e você obtém mais desempenho) se você simplesmente inicializar diretamente o campo correspondente:

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

Se você alterar actionText durante o tempo de vida do objeto correspondente MyAction, deverá colocar um método setter; caso contrário, inicialize o campo apenas no construtor sem fornecer um método setter.

Como dica e imagem são constantes, trate-as como constantes; tem campos:

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

Na verdade, ao projetar objetos comuns (não beans ou objetos que representam estritamente estruturas de dados), é uma má idéia fornecer setters e getters, pois eles meio que quebram o encapsulamento.

m3th0dman
fonte
4
Qualquer compilador competente e JITter na metade do caminho deve alinhar as chamadas setText () etc. do que não zero.
um CVn
0

Eu acho que isso é verdade se formos criar uma classe de ação genérica (como update, que é usada para atualizar Employee, Department ...). Tudo depende do cenário. Se uma classe de ação específica (como atualizar funcionário) (usada em muitos lugares do aplicativo - Atualizar funcionário) for criada com a intenção de manter o mesmo texto, dica de ferramenta e imagem em todos os locais do aplicativo (para o ponto de vista da consistência). Portanto, a codificação pode ser feita para texto, dica de ferramenta e imagem para fornecer o texto, dica de ferramenta e imagem padrão. Ainda para dar mais flexibilidade, para personalizá-los, ele deve ter os métodos setter correspondentes. Tendo em mente apenas 10% dos lugares, temos que mudar isso. A execução de texto de ação sempre do usuário pode causar texto diferente sempre para a mesma ação. Como 'Atualizar Emp', 'Atualizar funcionário', 'Alterar funcionário' ou 'Editar funcionário'.

Sandy
fonte
Eu acho que o construtor sobrecarregado ainda deve resolver o problema. Como em todos os casos "10%", você primeiro cria uma Ação com texto padrão e depois altera o texto da ação usando o método "setText ()". Por que não definir o texto apropriado ao construir a ação.
zswap
0

Pense em como as instâncias serão usadas e use uma solução que guie, ou até force, os usuários a usá-las da maneira correta, ou pelo menos melhor. Um programador usando essa classe terá muitas outras coisas para se preocupar e pensar. Esta classe não deve ser adicionada à lista.

Por exemplo, se a classe MyAction for imutável após a construção (e possivelmente outra inicialização), ela não deverá ter um método setter. Se na maioria das vezes ele usar o "Meu texto de ação" padrão, deve haver um construtor sem parâmetros, além de um construtor que permita um texto opcional. Agora, o usuário não precisa pensar em usar a classe corretamente 90% do tempo. Se o usuário geralmente deve pensar um pouco no texto, pule o construtor sem parâmetros. Agora, o usuário é forçado a pensar quando necessário e não pode ignorar uma etapa necessária.

Se uma MyActioninstância precisar ser mutável após a construção completa, você precisará de um setter para o texto. É tentador pular a configuração do valor no construtor (princípio DRY - "Não se repita") e, se o valor padrão geralmente for bom o suficiente, eu o faria. Mas, se não estiver, exigir o texto no construtor força o usuário a pensar quando deve.

Observe que esses usuários não são burros . Eles apenas têm muitos problemas reais para se preocupar. Ao pensar na "interface" da sua turma, você também pode impedir que ela se torne um problema real - e desnecessário.

RalphChapin
fonte
0

Na solução proposta a seguir, a superclasse é abstrata e tem todos os três membros configurados para um valor padrão.

A subclasse possui construtores diferentes para que o programador possa instancia-lo.

Se o primeiro construtor for usado, todos os membros terão os valores padrão.

Se o segundo construtor for usado, você fornecerá um valor inicial ao membro actionText, deixando outros dois membros com o valor padrão ...

Se o terceiro construtor for usado, você o instancia com um novo valor para actionText e toolTip, deixando imageURl com o valor padrão ...

E assim por diante.

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


}
Tulains Córdova
fonte