我在阅读an article的以下引文时提前阅读Alan Kay:
“许多所谓的面向对象语言都有设置器,当你在对象上有一个setter时,你会把它变回一个数据结构。”
这篇文章接着暗示这不是最佳做法,可能会损害您长期开发的应用程序。
我是一名具有C,Java,PHP,Ruby等经验的CompSci学生......所以我非常熟悉OOP作为一个概念和实践,但我绝不是专家。
在编写OO程序时,在访问字段值(使用getter),然后根据修改后的值RATHER创建新对象,而不是使用setter方法来简单地调整其中的字段,它在语义上是正确的(并且是最佳实践问题)对象
或者如果有一块木头,然后我从原始的一块上雕刻出一些木头,我是否应该将其塑造成质量较小的一块或完全新的一块。
使用setter看起来更简洁,如果你创建了这么多的对象,垃圾收集会成为一个问题,所以我从理论上而不是从实际的角度来看待这个问题。
提前感谢您的回答!
答案 0 :(得分:2)
对象必须至少具有以下两种职责中的一种:行为和知识。行为定义了对象如何响应其执行上下文中的事件,而知识定义了对象知道的内容。
行为是作为方法实现的,其中方法名称映射到触发反应的事件。知识被实现为getter,返回映射到被查询的知识的值。
以这种方式设计和实现,对象很少需要设置器,因为对象状态仅在对外部事件的反应中发生变化。话虽如此,可以将外部事件实现为setter(例如car.setSpeed(...)),但通常应该寻找更合适的名称(例如car.accelerateTo(...)和car.decelerateTo(。 ..))。
答案 1 :(得分:1)
这是一个设计问题。 OO的基本原理是封装。假设你有一个Circle对象。您可以为半径设置一个setter,为周边设置一个setter。但是,当然,由于两者都连在一起,设置周长也应该改变半径,设置半径也应该改变周长。如果不是这样,那么你确实没有一个对象,而是一个简单的数据结构,没有任何封装。
现在有些对象是可变的,有些则不是。例如,String或Integer是不可变的。一旦它有一个值,它的值就不会改变。将String连接到另一个String只会创建一个新的String实例,而不会影响两个原始字符串中的任何一个。不可变对象更容易理解和使用,因为它们只有一个状态。它们本质上也是线程安全的。但他们可以导致性能问题。例如,在循环中连接会创建大量消耗内存的临时String实例,并且必须是GC。这就是StringBuilder存在的原因:它基本上是一个可变的String。
总结:没有确定的答案,因为这完全取决于对象的类型和使用方式。通常,支持不可变对象而不是可变对象。偏向于可变对象,而不是可变对象。
答案 2 :(得分:0)
你问两个问题:
1)在对象中使用公共字段是否可以?
如果您通过方法访问对象的字段至少有一个原因,通常从维护/调试的角度来看更好:该方法引入了一个用于更改字段的公共条目,您可以添加自定义逻辑以防止无效更改或更新对象的不变量以使其状态与请求的更改保持一致。如果对象中没有内部状态,则不需要方法(但仍然可取:它为所有调试引入了一个断点区域进行调试)
2)我应该使用可变或不可变的对象吗?
两种方法都是正确的。不可变对象允许更容易推理代码,因为您无法修改现有对象的状态。 OTOH你可以得到更多的对象,所以要么会使用更多的内存,要么垃圾收集器会有更多的工作。可变对象提供相反的优点/缺点
答案 3 :(得分:0)
在我看来,这完全取决于你是否将有问题的对象建模为“不可变”。根据您的模型,这可能非常有意义,在这种情况下,您应该避免编写setter方法。
一般来说,我发现没有理由不使用setter。此外,您还可以找到许多语言(C#,AS3),它们提供了用于定义setter方法的专用语法。
答案 4 :(得分:0)
这完全取决于你想要达到的目标。如果要更改实例的属性,则应使用setter或类似方法(carveOutSomeWood()
)。如果您需要访问以前的版本并希望生成新实例,则可以实现某种createCopy()/clone()
方法来创建副本,然后将carveOutSomeWood()
应用于该新实例。
我不会通过读出旧实例的属性并自己创建它来推荐创建新实例。主要原因(可能还有其他原因)是,你的班级可能会改变。然后,您还必须更改新实例的创建,因为它是在类外部完成的。如果您只是使用createCopy()
方法,则应由班级负责解释其更改。
您还可以直接引入derive()
方法,在内部创建副本并应用一些更改。
答案 5 :(得分:0)
setter可以暴露你的内部实现并导致一些问题。 如果您设置了一个对象,并根据新的分配状态执行某些操作,那么如果在外部更改了分配的对象,则可能会出现问题。
答案 6 :(得分:0)
假设某个变量foo
标识了一个具有某个字段bar
且目前为foo.bar=5;
的类对象。人们希望foo.bar
等于六。您的问题是让foo
继续识别同一个对象,但是将该对象的bar
更改为6,或者让foo
识别新对象更好是否更好?就像它用来识别的那个,除了它的bar
字段等于6. 如果两种方法都是正确的,修改字段(无论是直接还是通过{{1}调用)比创建新对象更有效,因此会“更好”。但是,通常只有一种方法是正确的;另一个不会简单地“不那么好” - 它将错误。
确定使用哪种方法的$ 50,000问题是setFoo()
应该封装的内容以及由此确定的对象。
如果foo
包含对共享对象的引用,并且对它的其他引用使用它来封装“bar = 5”的状态,那么修改bar会是错误的。唯一正确的方法是创建一个新对象,并更改foo
以识别而不是原始对象。
如果foo
在Universe中的任何位置保存唯一标识特定对象的引用,那么创建新对象或修改现有对象在语义上是等效的,尽管如上所述修改现有的对象往往更快。
如果foo
标识了存在其他引用的对象,其目的是标识由其他引用标识的对象(实际上与其他引用形成连接),然后更改{ {1}}识别不同的对象是错误的,除非更新对该对象的所有引用以识别新对象。
如果字段foo
是foo
,或者以其他方式受到保护而不受修改,那么即使一个人拥有对特定对象的唯一引用,除了创建一个新对象之外别无选择让bar
识别它。
第二种情况和第四种情况可能是最常见的;那些可以通过“不要不必要地创建新对象”的原则来处理[在案例2中,可以避免创建新对象;在案例4中,它不能]。该原则也将轻松处理案例3 [因为它会鼓励人们做必要的事情]。从该规则的角度来看,唯一有问题的案例是第一个。即使在那里,关键是要认识到如果共享引用但不打算封装身份,则必须创建新对象。
我怀疑不可变对象流行的真正原因之一是正确使用可变对象需要知道哪些引用应该封装身份,哪些不应该封装;使用不可变类允许人们避免这样的问题。