今天我读了一本书,作者写道,在一个设计良好的类中,访问属性的唯一方法是通过其中一个类方法。这是一个被广泛接受的想法吗?为什么封装属性如此重要?不做的后果可能是什么?我之前在某处读过,这可以提高安全性或类似的东西。 PHP或Java中的任何示例都非常有用。
答案 0 :(得分:10)
这是一个被广泛接受的想法吗?
在面向对象的世界中,是的。
为什么封装属性如此重要?不做的后果可能是什么?
对象旨在成为包含其他对象可以通过公共接口以受控方式访问的数据和行为的内聚实体。如果一个类没有封装其数据和行为,它就不再能够控制被访问的数据,也无法完成与公共接口隐含的其他对象的契约。
这方面的一个主要问题是,如果一个类必须在内部进行更改,那么公共接口就不必更改。这样它就不会破坏任何代码,其他类可以像以前一样继续使用它。
PHP或Java中的任何示例都非常有用。
这是一个Java示例:
public class MyClass {
// Should not be < 0
public int importantValue;
...
public void setImportantValue(int newValue) {
if (newValue < 0) {
throw new IllegalArgumentException("value cannot be < 0");
}
}
...
}
这里的问题是因为我没有通过使importantValue
而不是公开来封装private
,所以任何人都可以出现并绕过我放入设置器的检查以防止对象产生无效的状态。 importantValue
永远不应该小于0,但缺乏封装使得无法阻止它。
答案 1 :(得分:3)
可能是什么后果 这样做?
封装背后的整个想法是所有与类相关的知识(除了它的接口)都在类本身内。例如,允许直接访问属性会导致确保任何分配对执行分配的代码有效。如果定义了什么是有效的更改,则必须通过使用类来审核所有内容以确保它们符合要求。将规则封装在“setter”方法中意味着您只需要在一个地方更改它,并且任何尝试任何有趣的调用者都可以获得异常抛出它作为回报。当属性发生变化时,您可能还需要执行许多其他操作,并且可以使用setter。
是否允许直接访问没有任何规则的属性来绑定它们(例如,任何适合整数的东西都可以)是好的做法是值得商榷的。我想使用getter和setter是一个好主意,为了保持一致性,即你总是知道你可以调用setFoo()
来改变foo
属性,而不必查询你是否可以直接做它们还允许您为您的课程提供面向未来的证明,这样如果您有其他代码要执行,那么放置它的位置已经存在。
就我个人而言,我认为必须使用getter和setter看起来很笨拙。我更愿意写x.foo = 34
而不是x.setFoo(34)
,并期待有一种语言为成员提供相当于数据库触发器的那一天,这些成员允许您定义在之前,之后或之后触发的代码作业。
答案 2 :(得分:1)
关于如何实现“好的OOD”的观点是十几个,而且非常有经验的程序员和设计师倾向于不同意设计选择和哲学。如果你向人们询问各种语言背景和范例,这可能是一个火焰战争的首发。
是的,理论上理论和实践都是一样的,因此语言选择不应该对高级设计产生太大影响。但在实践中他们会这样做,因此会发生好事和坏事。
让我补充一下: 这取决于。封装(使用支持语言)可以控制您如何使用类,因此可以告诉人们:这是API,您必须使用它。在其他语言(例如python)中,官方API和非正式(可变更)接口之间的区别仅在于命名约定(after all, we're all consenting adults here)
封装不是安全功能。
答案 3 :(得分:1)
思考的另一个想法
使用访问器进行封装还可以在将来提供更好的可维护性。在Feanor上面的回答中,它可以很好地执行安全检查(假设你的instvar是私有的),但它可以进一步获得好处。
考虑以下情况:
1)您完成申请,并将其分发给一些用户(内部,外部,等等)
2)BigCustomerA接近您的团队,并希望在产品中添加审计跟踪。
如果每个人都在他们的代码中使用访问器方法,那么实现这几乎是微不足道的。像这样:
MyAPI版本1.0
public class MyClass {
private int importantValue;
...
public void setImportantValue(int newValue) {
if (newValue < 0) {
throw new IllegalArgumentException("value cannot be < 0");
}
importantValue = newValue;
}
...
}
MyAPI V1.1(现在有审计线索)
public class MyClass {
private int importantValue;
...
public void setImportantValue(int newValue) {
if (newValue < 0) {
throw new IllegalArgumentException("value cannot be < 0");
}
this.addAuditTrail("importantValue", importantValue, newValue);
importantValue = newValue;
}
...
}
API的现有用户不会对其代码进行任何更改,现在可以使用新功能(审计跟踪) 如果没有使用访问器进行封装,您将面临巨大的迁移工作。
在第一次编码时,看起来似乎很多工作。输入class.varName = something
与class.setVarName(something);
的速度要快得多,但如果每个人都采用简单的方式,那么获得BigCustomerA功能请求的报酬将是一项巨大的努力。
答案 4 :(得分:1)
在 Object Oriente Programming 中,有一个原则被称为(http://en.wikipedia.org/wiki/Open/closed_principle): POC - &gt; 开放和封闭原则。这个原则适用于:应为可扩展性(继承)打开井类设计,但为修改内部成员(封装)而关闭。这意味着您无法在不考虑对象的情况下修改对象的状态。
因此,新语言只通过属性(C ++或Java中的getter和setters方法)修改内部变量(字段)。在C#属性中编译为MSIL中的方法。
C#:
int _myproperty = 0;
public int MyProperty
{
get { return _myproperty; }
set { if (_someVarieble = someConstantValue) { _myproperty = value; } else { _myproperty = _someOtherValue; } }
}
C ++ / Java的:
int _myproperty = 0;
public void setMyProperty(int value)
{
if (value = someConstantValue) { _myproperty = value; } else { _myproperty = _someOtherValue; }
}
public int getMyProperty()
{
return _myproperty;
}
答案 5 :(得分:0)
采取这些想法(来自 Head First C#):