使用构造方法还是setter方法?


16

我正在编写具有Action类的UI代码,例如:

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

创建此Action类时,几乎可以假定Action该类是不可自定义的(从某种意义上说,它的文本,工具提示或图像在代码中的任何位置都不会更改)。现在,我们需要在代码中的某些位置更改动作文本。因此,我建议我的同事从构造函数中删除硬编码的操作文本,并接受它作为参数,以使每个人都被迫传递操作文本。类似于下面的代码-

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

但他认为,由于setText()方法属于基类,因此可以在创建操作实例的任何位置灵活地使用该方法传递操作文本。这样,无需更改现有的MyAction类。所以他的代码看起来像这样。

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

我不确定这是否是解决问题的正确方法。我认为在上述情况下,用户还是要更改文本,所以为什么在构造动作时不强迫他呢?我在原始代码中看到的唯一好处是,用户可以创建Action类,而无需过多考虑设置文本。


1
您使用的语言不允许重载构造函数?
2012年

1
我正在使用Java.So,是的,它确实允许,我认为这可能是解决这一问题的一种方式
zswap 2012年

2
我要指出的是,如果事后没有公开设置班级成员的方法,那么您的班级实际上是不可变的。通过允许公共设置器,您的课堂现在变得可变了,如果您依赖不变性,则可能需要考虑到这一点。
cbojar 2014年

我要说的是,如果需要为您的对象设置有效的对象,则将其放在每个构造函数中...如果它是可选的(具有合理的默认值),并且您不关心不变性,则将其放在setter中。应该不可能将您的对象实例化为无效状态,或者在实例化之后尽可能将其置于无效状态。
比尔K

Answers:


15

我在原始代码中看到的唯一好处是,用户可以创建Action类,而无需过多考虑设置文本。

实际上,这并不是一个好处,对于大多数目的来说,这是一个缺点,在其他情况下,我称其为平局。如果有人在构造后忘记调用setText()怎么办?如果在某些异常情况下可能是错误处理程序,该怎么办?如果要真正强制设置文本,则需要在编译时强制设置文本,因为只有编译时错误实际上是致命的。在运行时发生的任何事情都取决于正在执行的特定代码路径。

我看到了两条明确的前进道路:

  1. 根据您的建议,使用构造函数参数。如果确实需要,可以传递null或使用空字符串,但是未分配文本的事实是显式的,而不是隐式的。可以很容易地看到null参数的存在,并发现其中可能存在一些想法,但是要查看缺少方法调用并确定是否缺少参数调用不是那么容易。对于像这样的简单情况,这可能就是我要采取的方法。
  2. 使用工厂/构建者模式。对于这样一个简单的场景,这可能是过高的,但是在更一般的情况下,它具有很高的灵活性,因为它允许您设置任意数量的参数,并在对象实例化之前或期间检查前提条件(如果构造对象是一个大操作和/或者该类可以以多种方式使用,这可能是一个巨大的优势)。尤其是在Java中,它也是一个常见的习惯用法,遵循您正在使用的语言和框架中已建立的模式很少是一件坏事。

10

构造函数重载将是一个简单直接的解决方案:

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

最好不要.setText稍后再调用,因为这样一来,无需覆盖任何内容,actionText就可以从一开始就成为预期的事情。

随着代码的发展,您将需要更大的灵活性(这肯定会发生),您将从另一个答案建议的factory / builder模式中受益。


当他们想要自定义第二个属性时会发生什么?
凯文·克莱恩

3
对于第二,第三,..属性,您可以应用相同的技术,但是要自定义的属性越多,它变得越笨拙。@ michael-kjorling在回答中说,从某种意义上讲,实施工厂/建造者模式将更有意义。
janos 2012年

6

添加流畅的“ setText”方法:

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

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

还有什么比这更清楚的了?如果您决定添加另一个可自定义的属性,则没问题。


我同意+1,并添加了另一个答案,以补充更多属性。我认为,当您拥有一个以上的单个属性作为示例时,更容易理解流利的API。
马查多2012年

我喜欢流畅的界面,特别是对于生成不可变对象的构建者!参数越多,效果越好。但是,看这个问题中的特定示例,我猜这setText()是在MyAction继承的Action类上定义的。它可能已经具有无效的返回类型。
GlenPeterson

1

就像kevin cline在回答中所说的那样,我认为要走的路是创建一个流畅的API。我想补充一点,当您拥有多个可以使用的属性时,流畅的API会更好地工作。

它会使你的代码更易读,而且从我的观点更加轻松,AHAM,“性感”来写。

在您的情况下,它会是这样的(很抱歉出现任何错字,距我编写上一个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;
    }
}

用法如下:

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

不好的主意,如果他们忘记设置文本或任何重要的东西该怎么办。
Prakhar '16

1

通常,使用构造函数或构造函数的建议是可以的,但是以我的经验,它错过了Actions的一些关键点

  1. 可能需要国际化
  2. 市场营销可能会在最后一刻发生变化。

我强烈建议从属性文件,XML等中读取名称,工具提示,图标等。例如,对于“文件打开”操作,您可以传入“属性”,然后它将寻找

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

这是一种很容易翻译成法语,尝试使用更好的新图标的格式,而无需程序员花费时间或重新编译。

这只是一个粗略的概述,还有很多东西留给读者...寻找国际化的其他示例。


0

在构造函数内部调用setText(actionText)或setTooltip(“ My action tool tip”)是没有用的;如果直接直接初始化相应的字段,则更容易(并且可以获得更高的性能):

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

如果在MyAction对应对象的生存期内更改actionText,则应放置一个setter方法;如果没有,则仅在构造函数中初始化字段,而不提供setter方法。

由于工具提示和图像是常量,因此将其视为常量;有字段:

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

实际上,在设计通用对象(不是bean或严格代表数据结构的对象)时,提供setter和getter是一个坏主意,因为它们会破坏封装。


4
任何中途胜任的编译器和JITter都应内联setText()等调用,因此,执行赋值的函数调用与将该赋值代替函数调用之间的性能差异最多应该可以忽略,并且更有可能比不为零。
CVn 2012年

0

我认为如果要创建通用操作类(例如update,用于更新Employee,Department ...),这是正确的。这全都取决于场景。如果创建了一个特定的操作(如更新员工)类(在应用程序中使用了很多位置-更新员工),其意图是在应用程序中的每个位置保留相同的文本,工具提示和图像(以确保一致性)。因此,可以对文本,工具提示和图像进行硬编码,以提供默认的文本,工具提示和图像。仍要提供更大的灵活性,要自定义这些设置,还应具有相应的设置方法。仅需记住10%的位置,我们就必须更改它。每次从用户处获取操作文本可能会导致同一操作每次产生不同的文本。像“更新员工”,“更新员工”,“更改员工”或“编辑员工”。


我认为重载的构造函数仍然可以解决问题。因为在所有“ 10%”情况下,您都将首先使用默认文本创建一个动作,然后使用“ setText()”方法更改该动作文本。为什么在构造动作时不设置适当的文本。
zswap 2012年

0

考虑如何使用实例,并使用一种解决方案,该解决方案将指导或什至迫使用户以正确或至少最佳的方式使用这些实例。使用此类的程序员还有很多其他事情要担心和思考。此类不应添加到列表中。

例如,如果假定MyAction类在构造(以及可能的其他初始化)后是不可变的,则不应具有setter方法。如果大多数情况下它将使用默认的“我的操作文本”,则应该有一个无参数的构造函数,以及一个允许可选文本的构造函数。现在,用户无需考虑90%的时间正确使用该类。如果用户通常应该对文本有所考虑,请跳过无参数构造函数。现在,用户被迫在必要时进行思考,并且不能忽略必要的步骤。

如果MyAction实例在完整构建后需要可变,那么您需要文本的二传手。诱使您跳过在构造函数中设置值的方式(DRY原理-“不要重复自己”),如果默认值通常足够好,我会这样做。但是,如果不是这样,则要求构造函数中的文本会迫使用户思考何时应该这样做。

请注意,这些用户并不傻。他们只是有太多实际问题要担心。通过考虑您的类的“接口”,您可以使它也不再是一个真正的问题-也是不必要的问题。


0

在以下提出的解决方案中,超类是抽象的,并且将所有三个成员都设置为默认值。

子类具有不同的构造函数,因此程序员可以实例化它。

如果使用第一个构造函数,则所有成员将具有默认值。

如果使用第二个构造函数,则为actionText成员提供一个初始值,而其他两个成员保留默认值...

如果使用第三个构造函数,则使用actionText和toolTip的新值实例化它,而使imageURl保持默认值...

等等。

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


}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.