封装——当设置器已经公开时,为啥我们需要它? [复制]

Posted

技术标签:

【中文标题】封装——当设置器已经公开时,为啥我们需要它? [复制]【英文标题】:Encapsulation - why do we need it when setters are already public? [duplicate]封装——当设置器已经公开时,为什么我们需要它? [复制] 【发布时间】:2015-12-12 17:29:46 【问题描述】:

封装是隐藏数据。我想在这里听到一些非常有趣的答案。

当我们已经为变量声明了 public 的 setter 方法时,保持变量为 private 的意义何在?

我了解封装的用法,但是当我们将设置器设为公开时,将变量保留为 private 的意义何在,我们可以直接使用 public 访问修饰符。

是因为我们不想让其他人知道我们在后端存储数据或管理数据的确切方式吗?

【问题讨论】:

而here可以被视为相反的问题。 我有一个大型系统,其中包含数十万或数百万行代码而没有一个设置器。我喜欢这样。 @McAdam331 是的,是的。提问者提到了 java,但是是的,它适用于所有使用 getter 和 setter 的语言。这是一个与oop相关的方面..谢谢:) 因为您可以选择要在 setter 中公开的内容。 这是另一种看待它的方式:cspray.net/programming/2012/05/13/… 【参考方案1】:

想一想:我通过一个班级代表一个现实生活中的对象,一只狮子。我会做这样的事情。

class Lion 
    public int legs;

现在其他一些开发人员需要我的类来创建一个对象并设置它的腿字段。他会做这样的事情

Lion jungleKing = new Lion();
jungleKing.legs = 15;

现在的问题是,Java 不会限制他将任何大于 4 的数字设置为该对象的腿数。这不是一个错误,它会运行得很好。但这是一个逻辑错误,编译器不会帮助你。这样一头狮子可以有任意数量的腿。 但是如果我们这样写代码

class Lion 
    private int legs;
    public void setLegs(int legs)
        if(legs > 4)
            this.legs = 4;
         
        else this.legs = legs;
     

现在你不会有任何超过 4 条腿的 Lion,因为更新类字段的策略已由类本身定义,并且任何不知道该策略的人都不会更新腿字段,因为更新legs 字段的唯一方法是通过setLegs() 方法,并且该方法知道类的策略。

【讨论】:

不错的答案,但最后一段可以使用一些逗号。【参考方案2】:

是不是因为我们不想让别人知道我们的确切方式 在后端存储数据或管理数据?

是的,这就是重点。它也与抽象信息隐藏的概念有关。

您提供了一个公共设置器,当类客户端调用该设置器时,该设置器将具有您记录的效果。如何实际实现这种效果与客户无关。您是否正在修改类属性之一?好的,让客户知道这一点,但不是您实际上正在修改变量的事实。将来,您可能希望修改您的类,以便它使用完全不同的东西(属性字典?外部服务?随便!)而不是简单的备份变量,并且客户端不会中断。

所以你的setter是一个抽象,你提供给客户“修改这个类属性”。同时,您隐藏您正在使用内部变量的事实,因为客户端不需要知道该事实。

(注意:这里我使用“属性”这个词作为一个通用概念,与任何具体的编程语言无关)

【讨论】:

抽象是关于隐藏功能..这与隐藏后端机制有关,我们设置数据的方式..很好的解释...:) 抽象是关于隐藏实现而不是功能。 功能取决于实现..所以是的,它又回到了同一件事上.. 但是,有(很多?)编程语言允许创建 setter/getter 属性,所以它们看起来像变量访问,但实际上是方法调用。访问的语法是相同的。因此,在这些语言中,如果您只关心能够修改您的类以使用不同的内部表示,那么您实际上不需要隐藏任何内容。 从一个除了改变私有变量之外什么都不做的公共设置器开始的另一个原因可能是在某些时候你可能想要“监管”正在设置的值。【参考方案3】:

将字段设置为私有文档一个强大的事实:这些私有字段仅在当前类中直接使用。这有助于维护人员不必跟踪字段使用情况。他们可以通过查看类并确定类环境对这些字段的影响通过公共和受保护的方法调用来更好地推理代码。它限制了类的暴露表面。

反过来,为私有字段定义“setter”并不是为了再次宣传它。它是关于声明另一个强大的事实:属于此类的 object 具有可以从外部修改的 property。 (术语 objectproperty 用于整体的有界部分关于这部分的可观察事实,不是 OOP 意义上的)

既然公开字段就足够了,为什么还要在字段上声明一个“setter”?因为声明一个字段不仅将一个名称绑定到类的对象的一个属性上,而且还承诺为这个属性使用内存存储.

因此,如果你声明一个“带有 setter 的私有字段”,你声明了三件事:

您声明您为字段/setter 集群提供的名称代表对象的一个​​属性,当对象被视为黑盒时,该属性会引起您的兴趣。 您声明此属性的值可以被对象的环境修改。 您声明在这个特定的具体类中,对象的属性是通过向其提交一些内存存储来实现的。

我主张您永远不要不分青红皂白地将您的字段设为私有的 getter 和 setter。字段用于描述存储。方法用于与环境的交互。 (而“getters”和“setters”的特殊情况是用于描述感兴趣的属性

【讨论】:

【参考方案4】:

***对 [mutator 方法(https://en.wikipedia.org/wiki/Mutator_method) 有很好的概述,这就是 setter 方法以及它们在不同语言中的工作方式。

简短版本:如果您想引入验证或其他在对象修改时执行的逻辑,最好有一个设置器来放入该逻辑。此外,您可能希望隐藏您存储事物的方式。因此,这些是拥有 getter/setter 的原因。同样,对于 getter,您可能具有提供默认值或依赖于例如的值的逻辑。诸如语言环境、字符编码等的配置。除了获取或设置实例变量之外,还有很多正当的理由想要拥有逻辑。

显然,如果您有 getter 和 setteres,您不希望人们通过直接操作对象状态来绕过它们,这就是为什么您应该保持实例变量私有的原因。

其他要考虑的事情包括你是否真的希望你的对象是可变的(如果不是,使字段成为最终的),你是否想修改对象状态线程安全,例如锁、同步等。

【讨论】:

【参考方案5】:

如果您确实想在分配之前进行范围检查怎么办?这是我使用 setter 和 getter 的情况之一

【讨论】:

【参考方案6】:

我完全同意Konamiman's answer,但我想补充一点:

在某些情况下,您真的不想要这种抽象。这很好。

我喜欢在这里使用的一个简单示例是一个用于 3 维浮点向量的类:

class Vector3f 
public:
    float x;
    float y;
    float z;
;

您能否将这些字段设为私有并提供设置器?当然,你可以。但是在这里您可能会争辩说,该类实际上只是应该提供一个浮点元组,而您不需要任何额外的功能。因此,添加 setter 只会使类复杂化,您宁愿将字段公开。

现在,您可以轻松构建以后可能会咬到您的场景。例如,您可能有一天会要求Vector3fs 不允许存储NaNs,如果有人尝试这样做,则应该抛出异常。但是这样一个假设的未来问题应该不足以证明引入额外的抽象是合理的。

作为程序员来决定哪些抽象对手头的问题有意义,哪些只会妨碍你完成工作。不必要的抽象是 over-engineering,它会像抽象不够一样损害您的工作效率。

底线:不要仅仅因为有人声称这是一种好习惯就盲目地到处使用 setter。相反,请考虑手头的问题并考虑权衡。

【讨论】:

好,就是我说的,看你实际需要什么。 取得正确的平衡(在过度抽象和欠抽象之间)可能很困难。 在任一方向弄错都会损害生产力。 一个更相关的场景恕我直言,如果 Vector 提供 SetLocation 函数,则 vector 的导数可能会在其位置更改时触发某些操作。例如,MoveReportingVector 可能在其构造函数中接受MotionReporter [一个接口] 对象,并在有东西在MoveReportingVector 上调用SetLocation 时调用其ReportMotion 方法。我希望 Java 有一种方法可以声明非 final 字段是公开可读但可在本地修改的,以促进此类类型,因为此类语义通常是合适的。【参考方案7】:

因为我们通过封装提供单点访问。假设你定义一个变量和它的 setter 如下

String username; 

public void setUsername(String username)
this.username = username;

稍后您想在设置用户名属性之前添加一些验证。如果您通过直接访问属性在 10 个位置设置用户名,那么您没有单点访问,您需要在 10 个位置进行此更改。但是,如果您有一个 setter 方法,那么通过在一个地方进行更改,您可以轻松实现结果。

【讨论】:

【参考方案8】:

虽然Konamiman 的answer 是正确的,但我想补充一点,在公共设置器与您所要求的直接公开公共字段的特殊情况下,还有另一个非常重要的区别需要牢记除了信息隐藏解耦实现与类的公共表面或API验证

在公共字段场景中,无法在修改字段时验证字段的值。在公共设置器(无论是Foo get; set; 属性还是SetFoo(Foo value))方法的情况下,您可以添加验证代码并启动所需的副作用,这样可以确保您的类始终处于有效或可预测的状态.

【讨论】:

【参考方案9】:

我在实践中遇到的或多或少简单而现实的例子是一个Options 类,它有很多setter 和getter。在某些时候,您可能想要添加依赖于他人或具有副作用的新选项。甚至用Enum 替换一组选项。在这种情况下,setA 函数不仅会修改a 字段,还会隐藏一些额外的配置逻辑。同样getA 不仅会返回a 的值,还会返回类似config == cStuffSupportingA 的值。

【讨论】:

以上是关于封装——当设置器已经公开时,为啥我们需要它? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

为啥在 Java 中使用自定义迭代器?

当我们已经拥有更强大的向量时,为啥还需要堆栈?

当我们已经有了一阶逻辑时,为啥还需要 PDDL?

当我们有 iframe 时为啥要使用 Shadow DOM?

当我们有常规数组时,为啥我们需要指向数组的指针?

当 set 在 Java 中已经是原子的时,为啥我们需要 compareAndSet?