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.


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.

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.


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.


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

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.


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.


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'.


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.


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


}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.