1

早些时候,当我看到Alan Kay的以下引用时,我正在阅读一篇文章

“许多所谓的面向对象的语言都有设置器,当你在一个对象上有一个设置器时,你就把它变成了一个数据结构。”

文章继续暗示这不是最佳实践,从长远来看可能会损害您开发的应用程序。

我是一名 CompSci 学生,具有 C、Java、PHP、Ruby 等方面的经验……所以我对 OOP 作为一个概念和实践相当熟悉,但绝不是专家。

我想我的问题归结为:

在编写 OO 程序时,访问字段值(使用 getter)然后根据修改后的值创建新对象,而不是使用 setter 方法来简单地调整该对象的字段,这在语义上是否正确(并且是最佳实践的问题)。

或者,如果有一块木头,然后我从原件上切下一些木头,我应该将其建模为质量较小的同一块,还是完全是一个新块。

使用 setter 似乎更简洁,如果您要创建如此多的对象,垃圾收集将是一个问题,所以我从理论而非实际的角度来处理这个问题。

提前感谢您的回答!

4

7 回答 7

2

一个对象必须至少具有以下两种职责中的一种:行为和知识。行为定义了对象如何对其执行上下文中的事件做出反应,而知识定义了对象知道的内容。

行为被实现为方法名称映射到触发反应的事件的方法。知识被实现为返回值映射到正在查询的知识的 getter。

以这种方式设计和实现的对象很少需要设置器,因为对象状态仅在响应外部事件时才发生变化。话虽如此,可以将外部事件实现为 setter(例如 car.setSpeed(...)),但通常应该寻找更合适的名称(例如 car.accelerateTo(...) 和 car.decelerateTo(. ..))。

于 2012-04-30T13:05:28.503 回答
1

这是设计问题。OO的基本原理是封装。假设您有一个 Circle 对象。你可以有一个半径的二传手和一个周长的二传手。但是当然,由于两者是联系在一起的,所以设置周长也应该改变半径,设置半径也应该改变周长。如果不是这样,你确实没有对象了,而是一个简单的数据结构,没有任何封装。

现在有些对象是可变的,有些则不是。例如,字符串或整数是不可变的。一旦它有了一个值,它的值就不能改变。将一个字符串连接到另一个字符串只会创建一个新的字符串实例,而不会影响两个原始字符串中的任何一个。不可变对象更容易理解和使用,因为它们只有一种状态。它们本质上也是线程安全的。但它们可能会导致性能问题。例如,在循环中连接会创建大量临时 String 实例,这些实例会消耗内存并且必须进行 GC。这就是 StringBuilder 存在的原因:它基本上是一个可变字符串。

总结:没有明确的答案,因为这完全取决于对象的种类和使用方式。一般来说,与可变对象相比,更喜欢不可变对象。喜欢不那么可变的对象而不是更可变的对象。

于 2012-04-30T12:44:45.750 回答
0

你问两个问题:

1)对象中有公共字段可以吗?

从维护/调试的角度来看,如果您出于至少一个原因通过方法访问对象的字段,通常会更好:该方法引入了一个用于更改字段的通用条目,您可以添加自定义逻辑以防止无效更改或更新不变量对象的状态以保持其状态与请求的更改一致。如果对象中没有内部状态,则不需要方法(但仍然建议:它为所有访问引入单个断点区域以进行调试)

2)我应该使用可变对象还是不可变对象?

任何一种方法都是正确的。不可变对象允许更轻松地对代码进行推理,因为您无法修改现有对象的状态。OTOH 你最终会得到更多的对象,所以要么会使用更多的内存,要么垃圾收集器会有更多的工作。可变对象提供相反的优点/缺点

于 2012-04-30T12:41:58.337 回答
0

在我看来,这完全取决于您是否将相关对象建模为“不可变”。根据您的模型,这可能非常有意义,在这种情况下,您应该避免编写 setter 方法。

不过一般来说,我没有理由不使用 setter。此外,您可以找到许多语言(C#、AS3),它们为定义 setter 方法提供了专门的语法。

于 2012-04-30T12:42:09.317 回答
0

这完全取决于您想要实现的目标。如果要更改实例的属性,则应使用 setter 或类似方法 ( carveOutSomeWood())。如果您需要访问以前的版本并想要生成一个新实例,您可以实现某种createCopy()/clone()方法来创建一个副本,然后应用carveOutSomeWood()到该新实例。

我不建议通过读取旧实例的属性并自己创建它来创建新实例。主要原因(可能还有其他原因)是您的班级可能会改变。然后,您还必须更改新实例的创建,因为它是在类之外完成的。如果您只是使用一个createCopy()方法,则该类将负责解释其更改。

您也可以直接引入一种derive()在内部创建副本并应用一些更改的方法。

于 2012-04-30T12:43:44.853 回答
0

setter 可能会暴露您的内部实现并导致一些问题。如果您设置一个对象,并根据新的分配状态执行一些操作,如果分配的对象在外部发生更改,则可能会导致问题。

于 2012-04-30T12:44:02.417 回答
0

假设某个变量foo标识了一个具有某个字段的类对象bar,并且现在foo.bar=5;。一想foo.bar等于六。您的问题是foo继续识别同一个对象是否更好,但将该对象更改bar为 6,或者识别一个新对象是否更好,foo它就像它用来识别的对象一样,除了它的bar字段将等于 6 . 在两种方法都正确的情况下,修改字段(无论是直接还是通过setFoo()调用)往往比创建新对象更有效,因此会“更好”。然而,通常只有一种方法是正确的;另一个不会简单地“不那么好”.

50,000 美元的问题是确定使用哪种方法应该封装什么foo以及由此识别的对象。

  • 如果foo持有对共享对象的引用,而对它的其他引用使用它来封装“bar = 5”的状态,那么修改bar是错误的。唯一正确的方法是创建一个新对象,并进行更改foo以识别该对象而不是原始对象。

  • 如果foo在宇宙中的任何地方拥有唯一标识特定对象的引用,那么创建新对象或修改现有对象在语义上将是等效的,尽管如上所述修改现有对象往往更快。

  • 如果foo标识存在其他引用的对象,并且其目的是标识由那些其他引用标识的对象(有效地与那些其他引用形成连接),那么更改foo以标识不同的对象将是错误的,除非所有引用都指向该对象被更新以识别新对象。

  • 如果 field baris final,或者以其他方式防止修改,那么即使一个人拥有对特定对象的唯一引用,也别无选择,只能创建一个新对象并foo对其进行标识。

第二种情况和第四种情况可能是最常见的;这些可以通过“不要不必要地创建新对象”的原则来处理[在情况2中,可以避免创建新对象;在第 4 种情况下,它不能]。该原则也将轻松处理案例 3 [因为它会鼓励人们做必要的事情]。从该规则的角度来看,唯一有问题的情况是第一个。即使在那里,关键是要认识到,如果引用是共享的但不打算封装身份,则必须创建新对象。

我怀疑不可变对象流行背后的真正原因之一是正确使用可变对象需要知道哪些引用应该封装标识,哪些不封装。使用不可变类可以避开此类问题。

于 2013-12-06T19:18:04.073 回答