【问题标题】:Is using a 'Data Class' a bad idea in this scenario? [closed]在这种情况下使用“数据类”是个坏主意吗? [关闭]
【发布时间】:2014-06-30 14:14:55
【问题描述】:

我目前正在使用您可以在工作中称为遗留代码的东西。我基本上有一个处理工作流的类,并且每次机器启动时只实例化一次。每次新用户使用这台机器并登录它时,都会启动一个新的“会话”,但我们保留这个类的相同实例。为了简单起见,我们假设这台机器是一台自助服务机器。

这基本上意味着每次新的用户会话开始时,我们都需要将所有流变量重置为默认值。

所以通常我会有实例/成员变量,例如:

boolean didUserPressYes;
boolean didUserTakeHisReceipt;
int numberOfInsertedItems;

然后是在流/用户会话开始时调用的 resetVars() 方法:

public void resetVars() {
 didUserPressYes = false;
 didUserTakeHisReceipt = false;
 numberOfInsertedItems = 0;
}

通常我只会重新实例化此类并避免使用此 resetVars() 方法。不幸的是,我们被绑定到一个不允许我们这样做的框架,我们必须保持相同的类实例。

在我们的实际代码中,我们有大约 12 个这样的变量,而且几乎肯定会在未来添加更多。我可以看到在每个用户会话开始时这些变量没有正确重置为其默认值的错误的巨大潜力 - 特别是如果有人新进入项目并忘记将他的新变量添加到此 resetVars() 方法中。

为了解决这个问题,我创建了一个包含这些变量的 WorkflowVariables 类,并在适当的地方具有 getter 和 setter,以及一些小的实用程序方法。在每个用户会话开始时,我们只需重新实例化这个 WorkflowVariables 类,现在就可以知道所有变量都已恢复为默认值。无需担心某些实例变量不会被重置,因为我们只有一个:

private WorkFlowVariables workFlowVariables;

public void resetVars() {
    workFlowVariables = new WorkFlowVariables();
}

但是,在阅读了一些“代码气味”之后,这似乎是所谓的“数据类”,并且实施起来是不好的做法。

在我的情况下,是否有人认为这是一个坏主意,或者这是否适合这种特定情况?也许有更好的方法来解决这个问题?如果我们保留多个实例变量,我是否过于担心潜在的错误?

提前致谢!

【问题讨论】:

  • 我认为您的方法没有问题。我或多或少会做同样的事情。 Data Class 还不错,在 Java/.NET 程序和其他几个平台中随处可见。

标签: java design-patterns anti-patterns


【解决方案1】:

您提到了一个在程序运行时存在的类。 Program 可能是这个类的好名字,但你可以想得更好。

--- 一个小问题,但我们会回到你的主线问题---

您还提到了有效范围是一个“会话”的变量。我将创建第二个类Session 并重构,直到这些变量在该类中。然后在构造函数中初始化变量。

在开始移动之前,请放入测试用例(尽可能)以验证正确的变量行为。移动后,清理这些测试用例并使其健壮。测试你通常不会做的事情,或者允许做的事情(如果你可以完全控制所有代码),比如传递空变量等。

通过这样做,您可以有效地取消保留一小部分代码。相信我,这将是值得的。

--- 好的,不要回到我们定期安排的问题---

你是对的,这条路径设置了一个数据类;但是,数据在正确的位置,现在只有逻辑在错误的位置。

查找访问集群中移动数据的代码块。如果直接访问数据,您可以通过将成员设为私有并观察整个产品的编译失败来做到这一点,或者使用 IDE 功能。跟踪每次访问,寻找读写行为模式。

一旦您确定了(假设)读取行为的模式,然后在“数据类”中创建一个虚拟方法,其名称与您认为正在发生的事情近似。将该方法放在远程调用者中,然后他们将语句“移动”到方法中。实际上,这会将它们转移到过去的纯数据类中。

现在你有了一个包含数据和一些行为的类。继续使用该模式,直到您对所有“正确”行为感到满意为止。然后再次检查您公开的“getter and setter”方法是否真的需要公开曝光。

在整个过程中保持您的测试是最新的,并验证它们是否也涵盖了新创建的方法。它们是您的安全网,您实际上没有更改任何内容,除非您打算更改代码。

祝你好运。

--- 在回答问题时编辑---

I will definitely go through my code and have a look to see where that's appropriate - some of the reading I mentioned suggested that. However, I do feel like because the Workflow Class (i.e. "Program class") already contains little/simple logic, that moving more of it over to the WorkFlowVariables ("Session") class for the sake of avoiding a Data Class seems a bit silly? 

只有出于愚蠢的原因才这样做是愚蠢的。您似乎认为移动代码的原因与避免气味有关。这根本不是移动代码的原因。移动代码的原因是它在错误的位置。

如果我有独立于我的数据的逻辑,那么我的数据必须在外部进行管理。也就是说,每次有人想对我的数据做某事时,都由“某人”以一种不会让我的数据内部不一致的方式来做。这可能包括在将数据类设置为包含变量的值之前检查变量是否不为空、根据成员变量的组合更新公开的值、在值更改时发送通知、标记项目以供“进一步处理”、存储审计日志条目等。

Won't it also tightly couple those classes? 

这在很大程度上取决于您在哪里进行拆分,以及如何进行拆分。重构并不意味着你可以一次性解决所有问题。这意味着您以不更改其功能的方式修改代码。在重构步骤之间,您最终将进行代码更改。如果没有事先的重构步骤,这些小的代码更改可能是不可能(或至少很难)进行的,并且它们可能会引入错误(就像任何非重构更改一样)。

哦,松散耦合在您为可扩展性而设计的地方非常有用。没有任何东西可以扩展的松散耦合是浪费资源编写您不需要的代码的典型示例。

松散耦合的一个很好的例子:

  1. 一种工作流引擎,可以通过接口调用未知数量的任务。

松耦合的一个坏例子:

  1. 当您需要执行步骤合同,但您决定让它们扩展步骤列表,因此您现在需要预处理步骤列表以确保它们符合合同。例如,您使用无限可配置的“工作列表”类作为数据库更新例程的基础,但您的数据库更新框架现在需要验证每个“工作列表”是否打开和关闭数据库。

当然,您可以使用数据库工作列表验证器“修复”此问题。这意味着您可能需要一个数据库工作列表“工作管理器”来确保在每个适当的时间点进行验证等。是的,您可以通过这种方式解决问题,但它会很脆弱,并且代码会表现出较差的数据局部性.

在后一个示例中,如果使用“数据库工作列表”对所需的打开和关闭步骤进行硬编码,中间步骤的数量可配置,则效果会好得多。

【讨论】:

  • 非常有帮助的答案 - 没想到有人能理解我的意思,但似乎你已经明白了。关于在类中添加一些行为,我肯定会仔细阅读我的代码并看看它在哪里是合适的——我提到的一些阅读表明了这一点。但是,我确实觉得因为工作流类(即“程序类”)已经包含很少/简单的逻辑,为了避免数据类,将更多的逻辑转移到工作流变量(“会话”)类似乎有点愚蠢的?它不会也将这些类紧密耦合吗?
  • 我更新了答案,以解决您的一些问题,尽可能从网络的这一端回答。
【解决方案2】:

这看起来不错,实际上可能是 State 模式,因此该类的更好名称可能是 SessionState

像状态一样思考它可以隐藏所有变量和条件逻辑。每个不同的州都会有不同的变量设置。根据哪个更能揭示您的用例的意图,从一种状态转换到下一种状态将设置变量或设置变量将转换状态(或两者兼而有之)。

【讨论】:

    猜你喜欢
    • 2013-05-20
    • 1970-01-01
    • 1970-01-01
    • 2010-12-27
    • 2012-04-19
    • 1970-01-01
    • 1970-01-01
    • 2021-05-23
    • 2019-09-26
    相关资源
    最近更新 更多