Effective Java:类和接口

来源:互联网 发布:动态域名解析软件排名 编辑:程序博客网 时间:2024/06/05 18:54

类和接口是Java程序设计的核心,它们也是Java语言的基本抽象单元。Java语言提供了许多强大的基本元素,供程序员用来设计类和接口。本章阐述的一些指导原则,可以帮助你更好地利用这些元素,设计出更加有用、健壮和灵活的类和接口。

第13条:使类和成员的可访问性最小化

设计良好的模块会隐藏所有的实现细节,把它的API与它的实现清晰地隔离开来。然后,模块之间只通过他们的API进行通信,一个模块不需要知道其他模块的内部工作情况。这个概念被称为信息隐藏或者封装(encapsulation),是软件设计的基本原则之一。

封装能够有效地在各个模块之间解耦合,使得模块能够独立地开发、测试、优化、使用、理解和修改。这样可以加快系统开发的速度,因为这些模块之间依赖很少,能够并行开发。并且减轻了维护负担,在调试它们的时候不影响其他模块。封装还提高了软件的可复用性,因为模块之间依赖很少,除了开发这些模块所使用的环境之外,它们在其他的环境中往往也很有用。最后封装也降低了构建大型系统的风险,因为即使整个系统不可用,但是这些独立的模块却有可能是可用的。

Java提供了许多机制来协助信息隐藏。访问控制机制决定了类、接口和成员的可访问性。实体的可访问性是由该实体声明所在的位置、以及该实体声明中所出现的访问修饰符共同决定的。

规则一:尽可能使每个类或者成员不被外界访问。换言之,应该使用与你正在编写的软件的对应功能相一致的、尽可能最小的访问级别。

如果一个包级私有的顶层类(或者接口)只是在某一个类的内部被用到,就应该考虑使它成为唯一使用它的那个类的私有嵌套类。这样可以将它的访问范围从包中的所有类缩小到了使用它的那个类。

在创建和销毁对象部分中提到了开发中有个实例,我需要为一个创建PDF的方法提供一个现成的对象,创建这个对象的构建器只有在组装这个对象的地方才会用到,因此没有必要像我目前这样,单独声明一个构建器的包级访问类。

如果方法覆盖了超类中的一个方法,子类中的访问级别就不允许低于超类中的访问级别。

如果一个类实现了一个接口,那么接口中所有的类方法在这个类中也都必须声明为公有的,因为接口中的所有方法都隐含着公有访问级别。

实例域决不能是公有的。如果域是非final的,或者是一个指向可变对象的final引用,那么一旦这个域成为公有的,就放弃了对存储在这个域中的值进行限制的能力,这意味着,你也放弃了强制这个域不可变的能力。同时,当这个域被修改的时候,你也是去了对它采取任何行动的能力。因此,包含公有可变域的类并不是线程安全的。即使域是final的,并且引用不可变的对象,当把这个域变成公有的时候,也就放弃了“切换到一种新的内部数据表示法”的灵活性。

同样的建议也适用于静态域,只是有一种例外情况。假设常量构成了类提供的整个抽象中的一部分,可以通过公有的静态final域来暴露这些常量。按惯例,这种域的名称由大写字母组成,单词之间用下划线分开。很重要的一点就是,这些域要么包含基本类型的值,要么包含指向不可变对象的引用。如果final域包含可变对象的引用,它便失去了final域的特性,虽然本身引用不可修改,但是所指向的对象却可以被修改,这个后果比较严重。

长度非0的数组总是可变的,因此类具有公有的静态final数组域,或者返回这种域的访问方法,这几乎总是错误的。如果类具有这样的域或者访问方法,客户端将能够修改数组中的内容。这是安全漏洞的一个常见的根源:

public static final Thing[] VALUES = { ... };

要注意许多IDE会产生返回指向私有数组域的引用的访问方法。修正这个问题有两种方法,一种是使公邮数组变成私有的,并增加一个公有的不可变列表:

private static final Thing[] PRIVATE_VALUES = {...};public static final List<Thing> VALUES = Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES));

另一种方法是使数组变成私有的,并添加一个公有方法,它返回私有数组的一个备份:

private static final Thing[] PRIVATE_VALUES = {...};public static final Thing[] values(){    return PRIVATE_VALUES.clone();}

总而言之,你应该始终尽可能地降低可访问性。在设计一个最小的公有API之后,应该防止把任何散乱的类、接口和成员变成API的一部分。除了公有静态final域的特殊情形之外,公有类都不应该包含公有域。并且要确保公有静态final域所引用的对象都是不可变的。

第14条:在公有类中使用访问方法而非公有域

在编写一些bean的时候,我通常的习惯是将所有的属性都私有化,能够提供给外界访问的属性都为其创建公有访问方法——getter和setter:

class 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(double x){this.x = x;}    public void setY(double y){this.y = y;}

如果类可以在它所在的包的外部进行访问,就提供访问方法,以保留将来改变内部表示法的灵活性。如果公有类暴露了它的数据域,想要在将来改变其内部表示法是不可能的,因为公有类的客户端代码已经遍布各处了。

然而,如果类是包级私有的,或者是私有的嵌套类,直接暴露它的数据域并没有本质的错误。比起访问方法的做法,直接访问数据域反而更不会产生视觉混乱。

让公有类直接暴露域虽然不是好的做法,但如果域是不可变的,这种做法的危害就比较小一些。如果不改变类的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;    }}

总之,公有类永远都不应该暴露可变域。暴露不可变域虽然危害比较小,但还是有问题的。有时候会需要用保级私有的或者私有的嵌套类来暴露域,无论这个类是可变的还是不可变的。

第15条:使可变性最小化

第16条:复合优先于继承

第17条:要么为继承而设计,并提供文档说明,要么就禁止继承

第18条:接口优于抽象类

第19条:接口只用于定义类型

第20条:类层次优于标签类

第21条:用函数对象表示策略

第22条:优先考虑静态成员类

0 0