方法链式调用的最佳实践("return this")

8
我有一个抽象类叫做DatabaseRow,在派生和构造后,主要从Load(object id)方法中加载。
我有很多代码创建一个新的实例类,从ID中加载它,然后返回该类。我想将这个代码简化成一行代码(只是为了整洁,有很多类只有列表属性返回这些已加载的实例)。
我能想到两种方法,但都不太对。
1. 我可以在我的Load方法末尾使用return this;并使用return new Derived().Load(id);
2. 我可以创建一个通用方法来返回一个已加载的方法。
public static T LoadRow<T>(object id) where T : DatabaseRow, new()
{
    T row = new T();
    row.Load(id);
    return row;
}

我看到一些其他代码使用与1号相同的方法,但我从未见过任何经验丰富的开发人员推荐它,也没有在.NET框架中发现执行相同操作的方法,所以这也许不是最佳实践?
有人知道是否有其他解决方案比这两个更好吗? 解决方案: 在阅读了SirViver的答案和评论之后,我意识到所有返回的属性都需要被缓存。解决方案略有不同,但类似于选项2(由于我没有解释设计的这一部分,我不希望任何人提出这个答案)
所有这些实例都将从数据库中检索到的值加载(如果您愿意,可以称之为数据库关系)。我创建了一个方法来通过列名加载实例,并在Dictionary中缓存加载的值。这对于DatabaseRow类的主要功能之一非常有效。
    private Dictionary<string, DatabaseRow> linkedRows;

    protected T GetLinkedRow<T>(string key) where T : DatabaseRow, new()
    {
        if (linkedRows.ContainsKey(key)) return (T)linkedRows[key];
        else
        {
            T row = new T();
            row.Load(this[key]);
            linkedRows.Add(key, row);
            return row;
        }
    }
4个回答

3

个人认为,在链式调用具有实际副作用的方法时,这是一种不好的做法。说实话,我认为这两个示例都是相当丑陋的“hack”,它们的唯一目的是节省两行代码。我认为结果并不更易读。

如果您希望立即加载记录,则可能更愿意提供一个构造函数变体,该变体采用您从中加载的ID,并使对象在构建时自动填充自身。但是,当我考虑它时,说实话,我根本不会费心——将更多信息塞入单行代码并不能使代码更易读和可维护。


在我的情况下,我有一个继承了DatabaseRow的类,并且有5个属性都使用相同的函数。类似于public Derived MyProperty { get { return new Derived().Load(myInt); } }这样的5行代码会比将属性分成多行代码更整洁。 - Connell
但是,属性代码的“整洁程度”到底有什么影响呢?在大多数情况下,Visual Studio 应该会折叠属性代码,而且你也不必经常查看类实现。话虽如此,构建和加载属性访问的数据似乎相当危险。如果有人不知道结果需要缓存,这很容易导致性能不佳或意外行为。 - SirViver
就是这样!!!我现在感觉自己像个白痴。你关于缓存的评论提醒了我,我已经在我的DatabaseRow类底部创建了这个函数!protected T GetLinkedRow<T>(string key) where T : DatabaseRow, new()会加载行并根据key的值将其缓存在字典中。 - Connell
1
既然我同意你在第一段所说的一切,而且你的评论触发了我的解决方案,我认为这应该是被接受的答案。谢谢。 - Connell

2

第一种方法在某些圈子里变得越来越流行;当有一个接口定义了大量这些方法并且可以组装成长链时,它经常被称为“流畅编程”。成功做到这一点的关键是永远不要定义一个有时返回this,有时返回null的方法。如果this总是被返回(除非有异常),那么它就是完美的风格。

个人而言,我不太喜欢第二种解决方案,因为它可能被认为违反了“一个职责”的原则。


1

虽然我个人喜欢返回this的方法,因为它允许它们被链接在一起,但我认为在.NET框架中(直到Linq),这是不被赞同的。我想到的原因是:

方法要么返回结果,要么改变对象的状态。返回this既改变了对象的状态,又返回了一个“结果”,但该结果是修改后的原始对象。这不符合用户的期望。

那怎么办呢:

public class Derived : DatabaseRow
{
    public Derived(object id):
    {
        Load(id);
    }
}

使用方法如下:

return new Derived(id);

我非常同意你在第二段的观点。实际上,我最初使用的是类似于你的解决方案,但我最终决定改用Load方法,因为我认为构造函数不应该具有太多功能(在这种情况下,执行SQL查询)。 - Connell
@Connell Watkins: 我之前也曾经考虑过构造器的问题,但现在我倾向于认为它们是确保对象处于可用状态的保证。因此,如果这些对象只有在调用Load方法后才能使用,那么你确实可以将它放在构造器中! - Daren Thomas

1

如果可以在不加载行的情况下进行操作,那么选项1才是可接受的。这是由于该模式允许您这样做:

return new Derived();

个人而言,我更喜欢静态方法。但我怀疑这只是个人偏好的问题。

正如ssg所说,另一个选项(我们称之为选项3)是在Derived中重载构造函数,这也可以工作。然而,拥有许多构造函数通常会很令人困惑,因为调用代码中没有描述正在发生什么的任何内容。

选项1:

return new Derived().Load(10);

选项2:

return Derived.Load(10);

选项三:

return new Derived(10);

选项1看起来像是创建了一个多余的对象。选项2很好,因为它做了它看起来要做的事情。选项3会让人困惑它到底做了什么。


有可能存在一个类但不被加载。如果你正在创建数据库中的新行,你会创建一个新实例,填充属性,然后调用Save()方法。你是指通用静态方法吗?还是每个派生类中的静态方法? - Connell
啊,我刚看到你的编辑。选项2看起来完美无缺,但问题在于这将要求我为此类型的每个派生类创建相同的静态方法,这似乎是不必要的。 - Connell

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接