封装确实有其目的,但也可能被滥用或滥用。
考虑像Android API这样的东西,它具有包含数十个(如果不是数百个)字段的类。暴露给API使用者的这些字段将使导航和使用变得更加困难,同时也给用户带来了错误的观念,即他可以对可能与应该如何使用它们冲突的那些字段执行自己想做的任何事情。因此,从可维护性,可用性,可读性以及避免疯狂的漏洞的角度而言,封装非常有用。
另一方面,POD或普通的旧数据类型(例如来自C / C ++的结构,其中所有字段都是公共的)也是有用的。在Lombok中使用无用的getter / setter(如@data批注生成的getter / setter)只是保持“封装模式”的一种方法。我们在Java中执行“无用” getter / setter的少数原因之一是方法提供了契约。
在Java中,接口中不能包含字段,因此可以使用getter和setter来指定该接口的所有实现者都具有的公共属性。在像Kotlin或C#这样的较新的语言中,我们将属性的概念视为可以声明一个setter和getter的字段。最后,除非Oracle向其添加属性,否则无用的getter / setters更是Java所必须承受的遗产。例如,Kotlin是JetBrains开发的另一种JVM语言,其数据类基本上可以完成Lombok中的@data注释。
这里还有一些例子:
class DataClass
{
private int data;
public int getData() { return data; }
public void setData(int data) { this.data = data; }
}
这是封装的不良情况。吸气剂和塞特剂实际上是无用的。大多使用封装,因为这是Java等语言的标准。除了在整个代码库中保持一致性之外,实际上并没有帮助。
class DataClass implements IDataInterface
{
private int data;
@Override public int getData() { return data; }
@Override public void setData(int data) { this.data = data; }
}
这是封装的一个很好的例子。封装用于强制执行合同,在这种情况下为IDataInterface。在此示例中进行封装的目的是使此类的使用者使用接口提供的方法。即使getter和setter没什么花哨的东西,我们现在也定义了DataClass和IDataInterface的其他实现者之间的共同特征。因此我可以有一个这样的方法:
void doSomethingWithData(IDataInterface data) { data.setData(...); }
现在,在谈论封装时,我认为同样重要的是也要解决语法问题。我经常看到人们抱怨强制封装而不是封装本身所需的语法。想到的一个例子是Casey Muratori(您可以在这里看到他的怒吼)。
假设您有一个使用封装的玩家类,并且希望将其位置移动1个单位。代码如下所示:
player.setPosX(player.getPosX() + 1);
如果没有封装,它将看起来像这样:
player.posX++;
他在这里辩称,封装会导致更多类型的输入而没有其他好处,这在很多情况下可能是正确的,但请注意。该参数违反语法,而不是封装本身。即使在缺乏封装概念的C语言中,您也经常会在以'_'或'my'前缀或后缀的结构中看到变量,或者表示它们不被API使用者使用的任何东西,就好像它们是私人的。
事实是封装可以帮助使代码更易于维护和使用。考虑此类:
class VerticalList implements ...
{
private int posX;
private int posY;
... //other members
public void setPosition(int posX, int posY)
{
//change position and move all the objects in the list as well
}
}
如果在此示例中变量是公共的,则此API的使用者将对何时使用posX和posY以及何时使用setPosition()感到困惑。通过隐藏这些详细信息,您可以帮助消费者以直观的方式更好地使用您的API。
但是,语法是许多语言中的限制。但是,较新的语言提供了一些属性,这些属性使我们可以很好地使用publice成员的语法以及封装的好处。如果使用MSVC,即使在C ++中,您也可以在C#,Kotlin中找到属性。这是科特林的一个例子。
class VerticalList:... {var posX:Int set(x){field = x; ...} var posY:Int set(y){field = y; ...}}
在这里,我们实现了与Java示例相同的功能,但是我们可以使用posX和posY,就像它们是公共变量一样。但是,当我尝试更改其值时,将执行setter set()的主体。
例如,在Kotlin中,这相当于实现了getters,setters,hashcode,equals和toString的Java Bean:
data class DataClass(var data: Int)
请注意,此语法如何使我们可以在一行中完成一个Java Bean。您正确地注意到了Java之类的语言在实现封装时遇到的问题,但这是Java的问题而不是封装本身。
您说过使用Lombok的@Data生成getter和setter。注意名称@Data。它主要用于仅存储数据的数据类,并应进行序列化和反序列化。想一想游戏中的保存文件。但是在其他情况下,例如与UI元素一样,您最明确地希望使用setter,因为仅更改变量的值可能不足以获取预期的行为。
"It will create getters, setters and setting constructors for all private fields."
-您描述此工具的方式,听起来好像是在维护封装。(至少在某种程度上是松散的,自动化的,有点贫血的模型。)那么到底是什么问题呢?