在公共类中使用访问方法而不是公共属性,使类和成员的可访问性最小化

发布时间:2019-10-04  栏目:编程  评论:0 Comments

Tips《Effective Java, Third
Edition》一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将近8年的时间,但随着Java
6,7,8,甚至9的发布,Java语言发生了深刻的变化。在这里第一时间翻译成中文版。供大家学习分享之用。书中的源代码地址:
9 API中的,所以JDK 最好下载 JDK 9以上的版本。但是Java 9
只是一个过渡版本,所以建议安装JDK 10。

Tips《Effective Java, Third
Edition》一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将近8年的时间,但随着Java
6,7,8,甚至9的发布,Java语言发生了深刻的变化。在这里第一时间翻译成中文版。供大家学习分享之用。书中的源代码地址:
9 API中的,所以JDK 最好下载 JDK 9以上的版本。但是Java 9
只是一个过渡版本,所以建议安装JDK 10。

图片 1Effective
Java, Third Edition

图片 1Effective
Java, Third Edition

有时候,你可能会试图写一些退化的类(degenerate
classes),除了集中实例属性之外别无用处:

类和接口是Java编程语言的核心。它们是抽象的基本单位。该语言提供了许多强大的元素,可以使用它们来设计类和接口。本章包含指导原则,帮助你充分利用这些元素,使你的类和接口是可用的、健壮的和灵活的。

// Degenerate classes like this should not be public!class Point { public double x; public double y;}

将设计良好的组件与设计不佳的组件区分开来的最重要的因素是,组件将其内部数据和其他组件的其他实现细节隐藏起来。一个设计良好的组件隐藏了它的所有实现细节,干净地将它的API与它的实现分离开来。然后,组件只通过它们的API进行通信,并且对彼此的内部工作一无所知。这一概念,被称为信息隐藏或封装,是软件设计的基本原则[Parnas72]。

由于这些类的数据属性可以直接被访问,因此这些类不提供封装的好处。
如果不更改API,则无法更改其表示形式,无法强制执行不变量,并且在访问属性时无法执行辅助操作。
坚持面向对象的程序员觉得这样的类是厌恶的,应该被具有私有属性和公共访问方法的类所取代,而对于可变类来说,它们应该被替换为setter设值方法:

信息隐藏很重要有很多原因,其中大部分来源于它将组成系统的组件分离开来,允许它们被独立地开发,测试,优化,使用,理解和修改。这加速了系统开发,因为组件可以并行开发。它减轻了维护的负担,因为可以更快速地理解组件,调试或更换组件,而不用担心损害其他组件。虽然信息隐藏本身并不会导致良好的性能,但它可以有效地进行性能调整:一旦系统完成并且分析确定了哪些组件导致了性能问题,则可以优化这些组件,而不会影响别人的正确的组件。信息隐藏增加了软件重用,因为松耦合的组件通常在除开发它们之外的其他环境中证明是有用的。最后,隐藏信息降低了构建大型系统的风险,因为即使系统不能运行,各个独立的组件也可能是可用的。

// Encapsulation of data by accessor methods and mutatorsclass Point { private double x; private double y; public Point(double x, double y) { this.x = x; this.y = y; } public double getX() { return x; } public double getY() { return y; } public void setX { this.x = x; } public void setY { this.y = y; }}

Java提供了许多机制来帮助信息隐藏。 访问控制机制(access control
mechanism)[JLS,6.6]指定了类,接口和成员的可访问性。
实体的可访问性取决于其声明的位置,以及声明中存在哪些访问修饰符(private,protected和public)。
正确使用这些修饰符对信息隐藏至关重要。

当然,对于公共类来说,坚持面向对象是正确的:如果一个类在其包之外是可访问的,则提供访问方法来保留更改类内部表示的灵活性。如果一个公共类暴露其数据属性,那么以后更改其表示形式基本上没有可能,因为客户端代码可以散布在很多地方。

经验法则很简单:让每个类或成员尽可能地不可访问。换句话说,使用尽可能低的访问级别,与你正在编写的软件的对应功能保持一致。

但是,如果一个类是包级私有的,或者是一个私有的内部类,那么暴露它的数据属性就没有什么本质上的错误——假设它们提供足够描述该类提供的抽象。在类定义和使用它的客户端代码中,这种方法比访问方法产生更少的视觉混乱。
虽然客户端代码绑定到类的内部表示,但是这些代码仅限于包含该类的包。
如果类的内部表示是可取的,可以在不触碰包外的任何代码的情况下进行更改。
在私有内部类的情况下,更改作用范围进一步限制在封闭类中。

对于顶层类和接口,只有两个可能的访问级别:包级私有(package-private)和公共的。如果你使用public修饰符声明顶级类或接口,那么它是公开的;否则,它是包级私有的。如果一个顶层类或接口可以被做为包级私有,那么它应该是。通过将其设置为包级私有,可以将其作为实现的一部分,而不是导出的API,你可以修改它、替换它,或者在后续版本中消除它,而不必担心损害现有的客户端。如果你把它公开,你就有义务永远地支持它,以保持兼容性。

Java平台类库中的几个类违反了公共类不应直接暴露属性的建议。
着名的例子包括java.awt包中的PointDimension类。
这些类别应该被视为警示性的示例,而不是模仿的例子。 如条目
67所述,暴露Dimension的内部结构的决定是一个严重的性能问题,这个问题在今天仍然存在。

如果一个包级私有顶级类或接口只被一个类使用,那么可以考虑这个类作为使用它的唯一类的私有静态嵌套类。这将它的可访问性从包级的所有类减少到使用它的一个类。但是,减少不必要的公共类的可访问性要比包级私有的顶级类更重要:公共类是包的API的一部分,而包级私有的顶级类已经是这个包实现的一部分了。

虽然公共类直接暴露属性并不是一个好主意,但是如果属性是不可变的,那么危害就不那么大了。当一个属性是只读的时候,除了更改类的API外,你不能改变类的内部表示形式,也不能采取一些辅助的行为,但是可以加强不变性。例如,下面的例子中保证每个实例表示一个有效的时间:

对于成员(属性、方法、嵌套类和嵌套接口),有四种可能的访问级别,在这里,按照可访问性从小到大列出:

// Public class with exposed immutable fields - questionablepublic final class Time { private static final int HOURS_PER_DAY = 24; private static final int MINUTES_PER_HOUR = 60; public final int hour; public final int minute; public Time(int hour, int minute) { if (hour < 0 || hour >= HOURS_PER_DAY) throw new IllegalArgumentException("Hour: " + hour); if (minute < 0 || minute >= MINUTES_PER_HOUR) throw new IllegalArgumentException("Min: " + minute); this.hour = hour; this.minute = minute; } ... // Remainder omitted}
  • private——该成员只能在声明它的顶级类内访问。
  • package-private——成员可以从被声明的包中的任何类中访问。从技术上讲,如果没有指定访问修饰符(接口成员除外,它默认是公共的),这是默认访问级别。
  • protected——成员可以从被声明的类的子类中访问(受一些限制,JLS,6.6.2),以及它声明的包中的任何类。
  • public——该成员可以从任何地方被访问。

总之,公共类不应该暴露可变属性。
公共累暴露不可变属性的危害虽然仍然存在问题,但其危害较小。
然而,有时需要包级私有或私有内部类来暴露属性,无论此类是否是可变的。

在仔细设计你的类的公共API之后,你的反应应该是让所有其他成员设计为私有的。
只有当同一个包中的其他类真的需要访问成员时,需要删除私有修饰符,从而使成员包成为包级私有的。
如果你发现自己经常这样做,你应该重新检查你的系统的设计,看看另一个分解可能产生更好的解耦的类。
也就是说,私有成员和包级私有成员都是类实现的一部分,通常不会影响其导出的API。
但是,如果类实现Serializable接口,则这些属性可以“泄漏”到导出的API中。

对于公共类的成员,当访问级别从包私有到受保护级时,可访问性会大大增加。
受保护(protected)的成员是类导出的API的一部分,并且必须永远支持。
此外,导出类的受保护成员表示对实现细节的公开承诺。
对受保护成员的需求应该相对较少。

有一个关键的规则限制了你减少方法访问性的能力。
如果一个方法重写一个超类方法,那么它在子类中的访问级别就不能低于父类中的访问级别[JLS,8.4.8.3]。
这对于确保子类的实例在父类的实例可用的地方是可用的(Liskov替换原则,见条目
15)是必要的。 如果违反此规则,编译器将在尝试编译子类时生成错误消息。
这个规则的一个特例是,如果一个类实现了一个接口,那么接口中的所有类方法都必须在该类中声明为public。

留下评论

网站地图xml地图