设计:Java 和在 setter 方法中返回自引用 [关闭]

Posted

技术标签:

【中文标题】设计:Java 和在 setter 方法中返回自引用 [关闭]【英文标题】:Design: Java and returning self-reference in setter methods [closed] 【发布时间】:2010-09-07 02:11:26 【问题描述】:

对于具有一长串经常使用的 setter 的类,我发现这种方式非常有用(尽管我最近阅读了 Effective Java 中的 Builder pattern 有点相同) .

基本上,所有 setter 方法都返回对象本身,因此您可以使用如下代码:

myClass
    .setInt(1)
    .setString("test")
    .setBoolean(true);

Setter 最后简单地返回这个:

public MyClass setInt(int anInt) 
    // [snip]
    return this;

你的意见是什么?优缺点都有什么?这对性能有影响吗?

在 c++ 中也称为named parameter idiom。

【问题讨论】:

就像 Joshua Bloch 的书,Effective Java (2nd edition),Builder Pattern 的优点之一,你可以更容易地推断出对象的状态,因为一旦它被创建,它不能变异。它还为类的客户端提供了更流畅的api,尤其是当类的参数很多时,其中许多是可选的。 看起来这在 C++ 中被称为命名参数习语。我通常赞成它而不是具有许多参数的传统构造函数。见***.com/questions/752762/…和cs.technion.ac.il/users/yechiel/c++-faq/… 【参考方案1】:

@pek 链式调用是 Java 7 的提议之一。它说如果方法返回类型为 void,它应该隐式返回 this。如果你对这个话题感兴趣,Alex Miller's Java 7 page 上有一堆链接和一个简单的例子。

【讨论】:

我发现这个成语可能是一个好主意(即使我自己不会使用它),但是如果可以通过显式声明 setter 的返回类型来完成,为什么要扩展语言非无效?我不喜欢对一种语言进行临时扩展以支持特定习语的想法。 我同意@Giorgio。这是“魔术”行为,所有魔术都是坏的。从某种意义上说,这很神奇,您无法检查源以找出它返回的内容,您必须知道 void 返回一个自我引用......我宁愿他们为此引入一个新的返回类型...称它为“自我”而不是“无效”。什么的。 @Tor Valamo:我同意你的看法。除此之外,我认为编程语言应该提供概念而不是习语。如果一个人真的想使用许多习语,应该使用像 Lisp 这样的可扩展语言,但不能指望一种语言会不断扩展以支持所有可能的习语:它们太多了,以至于一种语言很容易(不必要地)变得复杂. 将它构建到语言中的原因是,当您想要返回最多派生类型的可链接对象的层次结构时,它会变得很痛苦。目前,您必须拥有<Self> 泛型以允许子类更改返回类型,因此调用方法不会失去它们正在调用的对象的范围。构建到 jvm 中消除了这种丑陋。另外,它可以根据需要将对此的引用压入堆栈,而不是返回语句; “按需退货”而不是“始终退货”,在您不使用时很难优化。【参考方案2】:

这称为Fluent Interface,供参考。

就个人而言,我认为这是一个非常巧妙的想法,但确实是一个品味问题。我认为jQuery 是这样工作的。

【讨论】:

【参考方案3】:

我自己不会这样做,因为对我来说它混淆了特定方法的作用,并且方法链接对我的用处有限,而不是直接使用它。不过,这不会让我陷入愤怒和精神病的颤抖之球,这总是一件好事。 :')

我不会担心性能;问问 Knuth。

【讨论】:

有时我看到这些怪物大小的构造函数,我想到的是如果他们做了一些参数链接,代码会更容易理解。我更喜欢改进的可读性。当然,如果这一切都通过 vanilla setter 在单独的行上完成,那也不会那么糟糕。【参考方案4】:

我发现这在二传手中使用时风格很差。不可变类通常更适合链接,例如:

aWithB = myObject.withA(someA).withB(someB);

myObject 属于此类:

class MyClass 
    withA(TypeA a) 
         this.a.equals(a) ? this : new MyClass(this, a);
    

    private MyClass(MyClass copy, TypeA a) 
        this(copy);
        this.a = a;
    

构建器模式也很有用,因为它允许最终对象是不可变的,同时防止使用此技术时通常必须创建的中间实例。

【讨论】:

【参考方案5】:

这对建造者来说很有意义,你要做的就是设置一堆东西,创建真实的对象并将建造者扔掉。对于构建器,您不妨去掉方法名称的“set”部分。同样,不可变类型实际上并不需要“get”。

Something thing = new SomethingBuilder()
    .aProperty(wotsit)
    .anotherProperty(somethingElse)
    .create();

一个有用的技巧(如果你不介意每个类大约 2K 的运行时开销)是使用双括号习惯用法:

JFrame frame = new JFrame("My frame") 
    setDefaultCloseOperation(DISPOSE_ON_CLOSE);
    setLocation(frameTopLeft);
    add(createContents());
    pack();
    setVisible(true);
;

【讨论】:

这不是“成语”。这是对匿名类的可怕滥用,仅仅因为你给它一个漂亮的名字就不会被接受。 这是一个成语。它使代码不那么混乱。在我使用一种可以更清楚地表达它的语言之前,它对我有用。 +1:两个优点。为什么要投反对票?他列出了运行时开销的警告。 -1:这种晦涩难懂的技术并不广为人知,在维护中也很难发现。此外,与构建器类相比,它的缺点是在每个调用站点都引入了一个不同的类,而不是为每个人提供一个构建器类。 在不需要时使用内部类会在每次使用时发出额外的文件,并填满 PermGen 内存。我会考虑这种策略的唯一方法是,如果它在一个非常大的类中完成,您可能会从将功能拉出到新的类文件中获得延迟加载的好处。否则,您将支付运行时内存和过多的编译代码大小/类加载时间。【参考方案6】:

这个想法在c++中很常见,它允许操作级联......

例如

std::cout

是流方法

看到这个:http://www.java2s.com/Tutorial/Cpp/0180__Class/Cascadingmemberfunctioncallswiththethispointer.htm

【讨论】:

【参考方案7】:

我曾经是 Java(以及更糟糕的 C#)在整个对象中创建 getter 和 setter(获取 set 属性)实践的粉丝。这就是我认为的面向对象的用途,但实际上这导致我们只是暴露了对象的核心和实现,而不是真正利用封装。有时您无法摆脱这一点(想到 OR/M),但通常应该设置对象然后执行其功能。我梦想中的对象往往有一个或两个构造函数,并且可能有六个可以工作的函数。

这样做的原因是,一旦我开始开发 API,就真的需要让事情变得简单。您真的只想添加完成工作所需的复杂性,而 getter 和 setter 虽然本身很简单,但在大量添加时会增加堆的复杂性。当我以不同的顺序加载设置器时会发生什么?有什么不同吗?你确定吗?

【讨论】:

【参考方案8】:

再次@Dan,对于更复杂的情况(考虑到不变性),构建器模式是一个很好的解决方案。

另外,我主要同意你在吸气剂方面的看法。我相信您所说的是主要遵循“Tell don't ask”范式,我非常同意。但这主要面向吸气剂。

最后,以上所有内容都适用于具有大量属性的类。如果你只有不到 7 个,我看不出有任何理由。

【讨论】:

【参考方案9】:

在使用 Apache POI excel 库时,我经常这样做;我最终编写了链接的辅助方法,因此我可以应用格式、数据类型、内部单元格数据、公式和单元格定位。

对于有很多微小的轻量元素需要进行细微的微调的东西,它工作得很好。

【讨论】:

【参考方案10】:

如何使用

/**
 *
 * @author sanjay
 */
public class NewClass 
private int left ;
private int top;
public void set(int x,int y)
    
    left=x;
    top=y;

public NewClass UP(int x)
    
    top+=x;
    return this;

public NewClass DOWN(int x)
    
    top-=x;
    return this;

public NewClass RIGHT(int x)
    
    left+=x;
    return this;

public NewClass LEFT(int x)
    
    left-=x;
    return this;

public void Display()
    
    System.out.println("TOP:"+top);
    System.out.println("\nLEFT\n:"+left);


public static void main(String[] args) 
    // TODO code application logic here
    NewClass test = new NewClass();
    test.set(0,0);
    test.Display();
    test.UP(20).UP(45).DOWN(12).RIGHT(32).LEFT(20);
     test.Display();

【讨论】:

这个答案似乎没有解决 OP 的原始问题。【参考方案11】:

我同意@Bernard 的观点,即像这样的方法链接混淆了 setter 的目的。相反,我建议如果您总是创建这样的设置器链,那么您可以为您的类创建一个自定义构造函数,而不是

    MyClass
    .setInt(1)
    .setString("test")
    .setBoolean(true)
  ;

你会的

new MyClass(1,"test",true);

这使它更具可读性,如果您愿意,您可以使用它来使您的类不可变。

【讨论】:

实际上,如果你有大量的 setter 并且你需要调用的 setter 有很多变化,那么这会让它的可读性大大降低。尝试使用重载的构造函数来处理这将是一场噩梦,相信我。 我不同意。构造函数应该只具有创建时所需的参数。为方便起见,构造函数是为了弥补缺乏适当的构造来处理相同的语义。 污染构造函数是个坏主意,除非你有两个或三个参数,tops。将任何最终结果发送给构造函数,并为可变字段使用 setter。

以上是关于设计:Java 和在 setter 方法中返回自引用 [关闭]的主要内容,如果未能解决你的问题,请参考以下文章

为啥在 setter 方法中返回 $this?

Java基础笔记10

JAVA如何设计实体类?

三种违反面向对象编程风格的典型代码设计

Javaweb MVC设计思想

设计模式学习笔记 违反面向对象编程风格的三种代码设计及应对