重命名方法可以保留封装吗?


9

我正在阅读此页面,了解何时需要使用getter / setter,并且OP提供了以下代码示例:

class Fridge
{
     int cheese;

     void set_cheese(int _cheese) { cheese = _cheese; }
     int get_cheese() { return cheese; }
 }

void go_shopping(Fridge fridge)
{
     fridge.set_cheese(fridge.get_cheese() + 5);        
}

接受的答案状态:

顺便说一句,在你的榜样,我会给类FridgeputCheese()takeCheese()方法,而不是get_cheese()set_cheese()。这样您将仍然具有封装。

如何封装从对get / set重命名它来保存putCheese()/ takeCheese()你显然获取/设置一个值,那么为什么不干脆把它作为对get / set?

在相同的答案中,它还指出:

拥有getter和setter方法本身并不会破坏封装。破坏封装的方法是自动为每个数据成员(Java术语中的每个字段)添加一个getter和setter,而无需考虑任何问题。

在这种情况下,我们只有一个变量,cheese并且您可能想将奶酪拿回冰箱,所以在这种情况下,一对get / set是合理的。


7
putCheese会在冰箱中添加奶酪,然后takeCheese将其删除-这些是(较高级别的)面向领域的抽象,而不是对象字段获取和设置程序((较低级别的)计算机编程抽象)。
Erik Eidt

Answers:


17

我认为您错过了重点。它并不是说您应该重命名setter和getter,而是要具有添加和删除冰箱中物品的方法。即

public class Fridge
{
    private int numberOfCheeseSlices;

    public void AddCheeseSlices(int n)
    {
         if(n < 0) { 
             throw new Exception("you cant add negative cheese!");
         }
         if(numberOfCheeseSlices + n > this.capacityOfFridge) { 
             throw new Exception("TOO MUCH CHEESE!!");
         }
         ..etc
         this.numberOfCheeseSlices += n;
    }
}

现在私有变量被封装。我不能将其设置为所需的任何内容,只能使用这些方法从冰箱中添加和删除奶酪片,这又可以确保应用我想要的任何奶酪业务逻辑规则。


2
是的,但是您仍然可以保留它,因为setCheese()设置器中具有相同的逻辑。无论如何对我来说,您都试图通过重命名方法来隐藏使用setter的事实,但显然是getter / setting的东西。
QueenSvetlana

21
@QueenSvetlana不是二传手。这是一项手术。您要告诉冰箱“这是另一种奶酪”,它会将其添加到内部封装的奶酪中。设置员会说“您现在有5种奶酪”。这些是功能上不同的操作,而不仅仅是不同的名称。
蚂蚁P

1
您可以将其称为setCheese,但这会造成混淆,因为它不会设置奶酪的价值。
伊万

6
注意这里有一个整数溢出错误。如果已经有超过0种奶酪,则AddCheese(Integer.MAX_VALUE)应该失败,但不会失败。
user253751 '19

3
..etc部分中将全面介绍
Ewan,

5

根据定义,getter和setter 每次都破坏封装。什么可能被认为是,有时我们需要做的。有了这些,我的答案就变成了:

通过将封装从get / set重命名为putCheese()/ takeCheese(),如何保存封装?显然,您正在获取/设置一个值,为什么不简单地将其保留为get / set?

区别在于语义,即您所写内容的含义。封装不仅涉及保护,而且还隐藏内部状态。内部状态甚至在外部都不应该知道,而是希望该对象提供与业务相关的方法(有时称为“行为”)来操纵/使用该状态。

因此,getset都是技术术语,似乎与“冰箱”领域无关,而puttake实际上可能有一定道理。

然而,无论是put还是take实际意义仍取决于需求,不能客观地判断。参见下一点:

在这种情况下,我们只有一个变量,起司,您可能需要取起干酪并将其退回冰箱,因此在这种情况下,一对get / set是合理的。

没有更多上下文,就不可能客观地确定这一点。您的示例在go_shopping()其他地方包含一个方法。如果是这样的东西Fridge是比它并不需要get或者set,它需要什么Fridge.go_shopping()。这样,您就有了一种从需求中派生的方法,它所需要的所有“数据”都是本地的,并且您具有实际的行为,而不仅仅是薄薄的数据结构。

请记住,您并不是在构建通用的,可重用的Fridge。您正在构建Fridge仅用于您的需求。花更多的精力来使它超出所需的范围实际上是浪费的。


10
Getters and setters break encapsulation every single time-我的品味措辞太强烈了。方法(包括获取器和设置器)的目的与音乐会上的大门相同:它们允许有序进入和离开场地。
罗伯特·哈维

8
“根据定义,字母和设置员每次都破坏封装” -根据哪个定义?我也有一个问题,因为与其他任何公共成员一样,getter和setter只是公共接口的一部分。仅当它们公开设计中认为是内部(即由程序员(或团队)决定)内部实现细节时,它们才会破坏封装。吸尘器和设置器经常被随意添加,而耦合控制是事后的想法,这完全是另一回事。
菲利普·米洛娃诺维奇(FilipMilovanović),

2
@FilipMilovanović公平点。正如我在答案中提到的,“封装”用于隐藏对象内部(==对象状态==实例变量)。获取器和设置器发布对象内部。因此,根据这些定义,它们处于永久冲突中。现在,有时我们是否需要中断封装是另一回事,应单独处理。明确地说,通过说“ setters / getters”总是破坏封装,我并不是说它总是“错误的”,如果有帮助的话。
罗伯特·布劳蒂加姆(RobertBräutigam),

5
getter和setter并不“从字面上总是破坏封装”。getter和setter甚至常常不直接引用其下的成员,也不执行某种操作,或者仅专门定义它们,您可能只有getter或只有setter。当获取器和设置器仅执行成员访问并且都为同一字段定义时,它破坏了封装。对于不想通过内部更改破坏ABI / API并避免客户端重新编译的库维护者来说,甚至这也是必要的。
WHN

4
好的,getter和setter只能在99%的时间内破坏封装。快乐?而且,通过公开封装对象的类型,它们肯定会破坏封装。一旦拥有了public int getFoo(),实际上几乎不可能更改为另一种类型。
user949300 '19

3

几乎所有这些都表明了封装及其应用的基本误解。

您破坏封装的最初响应是错误的。您的应用程序可能需要简单地在冰箱中设置奶酪的价值,而不是增加/减少或添加/删除。同样,它不是语义,无论您用什么名称,如果您需要访问和/或更改属性,都不会通过提供它们来破坏封装。最后,封装并不是真正意义上的“隐藏”,它是关于控制对状态和值的访问,这些状态和值不需要在类外公开或操纵,而应将其授予应有的状态和值并执行内部提供的任务。

当有合法需要获取或设置值时,getter或setter不会破坏封装。这就是为什么方法可以公开的原因。

封装是关于将数据和将数据直接修改在一起的方法放在一个逻辑位置(即类)中。

在这种特定情况下,显然需要在应用程序中更改奶酪的价值。无论如何完成此操作,通过get / set或add / remove,只要将方法封装在类中,您都将遵循面向对象的样式。

为了澄清起见,我将举一个示例,说明如何通过提供访问权限来破坏封装,而不管方法名称或逻辑执行如何。

假设您的冰箱有“使用寿命”,只是在冰箱不再运行之前的几个滴答声中(出于争论的原因,冰箱无法维修)。从逻辑上讲,用户(或应用程序的其余部分)无法更改此值。它应该是私有的。只有通过说一个不同的公共属性“ isWorking”才能看到它。当使用寿命到期时,冰箱内部将isWorking设置为false。

生命周期倒计时的执行以及isWorking开关的翻转都在冰箱内部,外部没有/应该能够影响该过程。isWorking应该仅可见,因此吸气剂不会破坏封装。但是,为生命周期过程的元素添加访问器将破坏您的封装。

像大多数事物一样,封装的定义不是文字的,而是相对的。您应该可以在课程之外看到X吗?您应该可以更改Y吗?是适用于您的对象的所有内容都在此类中,还是功能分布在多个类中?


让我们从务实的角度来看它。你的意思是如果代码封装不破打算这样一来,或者如果有一个“合法”的理由。这基本上意味着我们永远不能说封装被破坏了,因为开发人员只需要说她“打算”那样,或者有“合法”的理由。所以这个定义基本上是没有用的,不是吗?
罗伯特·布劳蒂加姆(RobertBräutigam)

不,我是说封装并不会因为提供访问权限而被破坏。它与程序员所说的无关,而与应用程序的需求有关。应用程序需要具有操作对象的访问权限,封装是关于控制访问权限的。应用程序可能需要“设置”对象的属性,但是执行该“设置”操作所需的逻辑和/或计算应封装在对象类中。
user343330

我认为这是我们根本不同的地方,您说:“应用程序可能需要“设置”对象的属性...”。我不这么认为。选择涉及设置或获取属性的设计取决于开发人员。这是一个选择!有很多方法可以编码某些东西,但并非所有方法都涉及获取或设置实例变量值。
罗伯特·布劳蒂加姆(RobertBräutigam),

可能是...通常,设计基于满足需求而定,无论程序员选择哪种方案都基于业务环境。但是,无论哪种情况,如果应用程序基本需要操作作为对象一部分的值,则必须以某种方式使该功能可访问。提供执行工作的入口点不会破坏封装。采取销售应用程序。在某个时候,您的交易应该计算税金,在交易对象中这样做会使其保持封装状态,但是应用程序应该能够声明交易
。CalcTax– user343330

设置器由于其简单性而成为不好的例子,请考虑一个函数,该函数执行的工作要多得多,导致对象属性被更新,而类外部的工作却被执行,然后说object.attribute = x。第一个是良好的封装,同时提供适当的访问权限,第二个则不是。至于吸气剂,应用程序是否只需要知道对象的那一部分即可做对象类中无法包含的其他事情?如果是,则将其暴露不会破坏您的封装。如果否,则将其封装在对象类中
user343330 '19

1

这不仅是重命名方法。两种方法的功能不同。

(请记住此图)

get_cheese和set_cheese暴露了奶酪。putCheese()和takeCheese()会将奶酪隐藏起来,并进行管理,并为用户提供一种处理方法。观察者看不到奶酪,他/她只看到两种操作奶酪的方法。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.