【问题标题】:Is it better to modify an object directly or to return a modified clone of the object?直接修改对象还是返回对象的修改克隆更好?
【发布时间】:2014-12-06 18:23:46
【问题描述】:

问题是我无法从内部修改我正在编写的对象,因此我创建了一个我修改并返回的克隆。但是,在其他函数中,我直接修改调用该方法的对象。我希望,人们也建议,保持一致,这样用户就可以对返回的克隆做任何他们喜欢的事情,而无需修改实际对象。

我必须在某些函数中返回对象的修改克隆,因为没有办法绕过它(我知道)。除了标题中的问题之外,是否有一致的做事方式更好(即使是最轻微的更改也会让我返回克隆)或者如果我有不同的方式来响应同一个班级的用户是否可以?

[编辑] 这样做更好吗:

 public Image fillWithColor(Color fillColor) {
    Image newImage = new Image(this.getWidth(), this.getHeight(), this.getType());

    for (int x = 0; x < this.getWidth(); x++) {
        for (int y = 0; y < this.getHeight(); y++) {
            newImage.setPixel(x, y, fillColor.getRGB());
        }
    }

    return newImage;
}

或者这个:

public void fillWithColor(Color fillColor) {
    for (int x = 0; x < this.getWidth(); x++) {
        for (int y = 0; y < this.getHeight(); y++) {
            this.setPixel(x, y, fillColor.getRGB());
        }
    }
}

【问题讨论】:

  • 这其实是一个很重要的问题!不要投票关闭。这基本上是对象是否应该是可变的问题。
  • 这完全取决于您要对下游对象做什么,以及您为使用这些对象所采用的约定。对于某些程序,这两种方法都可能更有效,而对另一些程序则错误。
  • 我认为返回“newImage”并仍然操纵源真的很奇怪。我还认为假设调用像“fillWithColor”这样在对象上不返回任何内容的方法不会修改对象是非常糟糕的。

标签: java object clone immutability


【解决方案1】:

出于各种重要原因,大趋势是将尽可能多的数据视为只读数据。今天甚至数据库也这样做。

您显然已经认识到,不受控制地修改数据会给您带来麻烦。很好。尝试将您的数据设计为不可变对象,从长远来看,许多事情会变得容易得多。请注意,Java 中的某些数据结构本质上是可变的(数组、哈希表等),并且注定会发生变异。

在上面的示例中,我会选择第一个变体。为什么?复制图像可能需要几微秒和一些 RAM,而不是就地更新。但是您可以保留旧图像,并且根据您的应用程序,这可能是有益的。此外,您可以在 10 个不同的线程中并行使用 10 种不同的填充颜色为同一图像着色,并且不会出现锁定问题。

话虽如此,仍然无法回答您的问题,例如“总是更好......”。这取决于您的问题、环境、编程语言、您使用的库以及许多其他因素。

因此,假设大多数情况下不可变数据是可取的,除非有严重的理由反对它。 (节省几微秒的执行时间通常不是一个严重的原因。)

换句话说,应该有充分的理由使数据类型可变,而不变性应该是默认值。不幸的是,Java 不是支持这种方法的语言,相反,默认情况下一切都是可变的,需要一些努力才能使其与众不同。

【讨论】:

  • 感谢您冗长而详尽的回复。正如你所建议的,我将修改返回克隆的方法,而不是直接修改图片,因为我不需要过多担心速度和内存。
【解决方案2】:

唯一正确的答案是:

视情况而定”。

这就是工程的全部意义所在。做出正确的权衡。假设您是一名为城市工作的工程师,负责为市中心设计新的垃圾桶。你有一些决定要做。你想让它们变大,这样它们就可以容纳很多垃圾,并且在忙碌的日子里不会溢出。但是你也想让它们变小,这样它们就不会在人行道上占用太多空间。你想让它们轻一些,以便在清空它们时可以轻松处理它们,而重一些则它们不会被风吹倒或被流氓踢倒。所以这不是大或小、重或轻的问题,而是有多大和多重的问题。

在软件工程中,还有许多相互排斥的品质,您必须在项目中做出正确的选择。一些例子:

  • 延迟与吞吐量 - 您是想要快速响应请求,还是想要完成大量工作;
  • 内存与 cpu - 您是否可以使用大量内存作为查找表,或者您是否会花费 cpu 时间来计算答案。

不可变与可变

不可变类型的优点是它们是线程安全的。线程 A 和 B 都可以引用同一个对象,并且仍然可以确保在不使用锁的情况下其值不会意外更改。如果线程 A 想要更改值,那么它可以通过将其持有的引用更改为新对象来实现;线程 B 仍然高兴地抓住原始对象。

对象的值意外更改不仅是并发编程中的问题,而且还可能在您的类的用户没有预料到的情况下发生。这就是为什么会有defensive copying 的概念。

java 中的 stock Date 类是可变的。所以考虑一个带有getBirthDate() getter 和setBirthDate() setter 的Person 类。作为Person 类的用户,您希望只能使用setter 更改此人的出生日期,但如果您的getter 没有返回防御性副本,那么该类的用户也可以意外更改它通过更改从getBirthDate() 接收到的Date 对象。

因此,不可变类型使程序线程安全(r)并且不易出错,因此通常是一个好主意,但您不能总是使用它们。您的 fillWithColor() 函数是一个不可行的示例。

Canvas 类是一个可变对象。您将拥有fillWithColor() 函数,还有drawLine()drawElipse()drawText() 等等。使用这些函数构建绘图可能需要多次调用。考虑画一个“no parking”交通标志:

  1. 用背景色填充
  2. 画一个红圈
  3. 在里面画一个白圈
  4. 在里面画一个黑色的P
  5. 在其上画一条红色对角线

如果您的 Canvas 类是不可变的,您将需要五倍的内存量和五倍的像素数。这真的是一个微不足道的例子。考虑 this 猎豹的 SVG 图像。它背面的每个点都是对绘图函数的调用。

视情况而定

我会说你应该尽可能使用不可变类型,而在你不能使用的地方使用不可变类型。通常,这种划分属于小数据类型和大数据类型。

如果您的类型引用的数据结构小到足以成为值类型,那么它可能应该是不可变的引用类型。就像 java Date 应该是不可变的,毕竟它只有 8 个字节。 如果您的类型引用了一些大的东西并且您需要允许对其进行许多操作,那么您将必须务实并使其成为可变类型。就像您的 Canvas 示例一样,毕竟图像可以是兆字节大。

可变和不可变都是必需的。

【讨论】:

  • 那么,为了节省内存和时间,您是否建议将我的对象设置为可变的,并且仍然具有返回而不是直接修改的某些函数?
  • 我同意“视情况而定”。我建议你尽一切可能让清楚什么是不可变的,什么不是。除了返回 void 的变异方法和返回“克隆”对象的非变异方法之外,我会选择一种使行为明显的方法命名约定。例如,更改画布背景的方法可能称为“setBackgroundColor”,而克隆画布并设置克隆背景的方法可能称为“withBackgroundColor”,以便您获得“canvas.setBackgroundColor(color)”与. "canvas.withBackgroundColor(color)"
  • @sjbhalli 是的,让 `fillWithColor ()' 改变调用该函数的对象上的像素。在不了解更多信息的情况下,我无法讲述其他功能(这取决于)。其他绘图函数应该发生变化,但是如果那里有一个出生日期属性,那么你应该有一个返回防御性副本的 getter。
猜你喜欢
  • 2023-03-25
  • 1970-01-01
  • 1970-01-01
  • 2016-02-21
  • 2012-06-30
  • 2020-03-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多