effective java(类和接口)

1、使类和成员的可访问性最小化

要区别设计良好的模块和设计不好的模块,最重要的因素在于,这个模块对于外部的其它模块而言,是否隐藏其内部数据和其它实现细节。

设计良好的模块会隐藏所有的实现细节,把它的API与它的实现清晰地隔离开来。然后,模块之间通过API进行通信,这个概念被称为信息隐蔽或封装。

地一条规则:尽可能地使每个类或者成员不被外界访问。

对于成员:

private:

package:缺省的

protected:任何子类可以访问这个成员,并且,声明该成员的包内部的任何类也可以访问这个成员。

public:

有一个规则限制了降低方法的可访问性的能力。如果方法覆盖了超类中的一个方法,子类中的访问级别就不允许低于超类中的访问级别。

这条规则有种特殊情形:如果一个类实现了一个接口,那么接口中所有的类方法在这个类也必须被声明为共有的。接口中的所有方法都隐藏了共有访问级别。

2、在公有类中使用访问方法而非公有域

如果类可以在它所在的包的外部进行访问,就提供访问方法,以保留将来改变该类的内部表示法的灵活性。

如果类是包私有的,或者是所有的嵌套类,直接暴露它的数据域并没有本质的错误——假设这些数据域确实描述了该类所提供的抽象。

这种方法比访问方法的做法更不会产生视觉混乱,无论是在类定义中,还是在使用该类的客户端代码中。虽然客户端代码与该类的内部表示法紧密相连,但是这些代码被限定在包含该类的包中。


3、使可变性最小化

为了使类成为不可变,要遵循下面五条规则:

a.不提供任何会修改对象的方法。

b.保证类不会被修改。这样可以防止粗心或者恶意的子类假装对象的状态已经改变,从而破坏该类的不可变行为。为了防止子类化,一般做法使这个类成为final的。

c.使所有的域都是final的。

d.使所有的域都成为私有的。

e.确保对于任何可变组件的互斥访问。

如果类具有指向可变对象的域,则必须确保该类的客户端无法获得指向这些对象的引用。并且,永远不要用客户端提供的对象引用来初始化这样的域,也不要从任何访问方法中返回该对象的引用。在构造器、访问方法和readObject方法中请使用保护性拷贝技术。


4、复合优先于继承

继承是实现代码重用的有力手段,当它并非永远是完成这项工作的最佳工具。但不当的使用会使软件变的很脆弱。在包内部使用继承是非常安全的,在那里,子类和超类的实现都处在同一个程序员的控制下。

对于专门为继承设计、并且具有很好的文档说明的类来说,使用继承也是非常安全的。然而,对普通的具体类进行跨越包边界的继承,则是非常危险的。

(这里说的继承是特对于“类的继承,当一个类扩展另一个类的时候”。不适用于“接口继承”,当一个类实现一个接口,或者当一个接口扩展另一个接口。)

复合:不用扩展现有的类,而是在新的类中增加一个私有域,它引用现有类的一个实例。这种设计被称做“复合”,因为现有类变成了新类的一个组件。新类中的每个实例方法都可以调用被包含的现有类实例中对应的方法,并返回它的结果。这被称为转发。

新类中的方法被称为转发方法。

只有当子类真正是超类的子类型时,才适合用继承。换句话说,对于两个类A和B,只有当两者确实存在“is-a”关系的时候,类B才应该扩展类A。

简而言之,继承的功能非常强大,但是也许存在诸多问题,因为它违背了封装原则。只有当子类和超类之间确实存在子类关系时,使用继承才是恰当的。即便如此,如果子类和超类处在不同的包中,并且超类不是为继承而设计的,那么继承将会导致脆弱性。

5、要么为继承而设计,并提供文档说明,要么就禁止继承

6、接口优于抽象类

a、现有的类可以很容易被更新,以实现新的接口。

b、接口是定义mixin的理想选择。

c、接口允许我们构造非层次结构的类型框架。

7、接口只用于定义类型

当类实现接口时,接口就充当可以引用这个类的实例的类型。

8、类层次优先于标签类

标签类方式:

class Figure {
enum Shape {RECTANGLE, CIRCLE};
final Shape shape;
double length;
double width;
double radius;
public Figure(double radius) {
shape = Shape.CIRCLE;
this.radius = radius;
}
public Figure(double length, double width) {
shape = Shape.RECTANGLE;
this.length = length;
this.width = width;
}
double area() {
switch (shape) {
case RECTANGLE:
return length * width;
case CIRCLE:
return Math.PI * (radius * radius);
default: 
throw new AssertionError();
}
}
}

类层次方式:

abstract class Figure {
abstract double area();
}


class Circle extends Figure {
final double radius;
public Circle(double radius) {
this.radius = radius;
}
@Override
double area() {
return Math.PI * (radius * radius);
}
}


class Rectangle extends Figure {
final double length;
final double width;
public Rectangle(double length, double width) {
this.length = length;
this.width = width;
}
@Override
double area() {
return length * width;
}
}

9、用函数对象表示策略

有些语言支持函数指针(function pointer)、代理(delegate)、lambda表达式(lamdba expression),或者支持类似的机制,允许程序把 “调用特殊函数的能力”存储起来并传递这种能力。这种机制通常用于允许函数的调用者通过传入第二个函数,来指定自己的行为。如,C语言标准库中的qsort函数要求用一个指向comparator(比较器)函数的指针作为参数,它用这个函数来比较待排序的元素。这正是策略(strategy)模式的一个例子,比较器函数代表一种为元素排序的策略。

调用对象上的方法通常是执行该对象上的某项操作。然而,我们也可能定义这样一种对象,它 的方法执行其他对象上的操作。如果一个类仅仅导出这样的一个方法,它的实例实际上就等同于一个指向该方法的指针。这样的实例被称为函数对象。

class StringLengthcomparator {

public int compare(String s1, String s2) {

return s1.length() - s2.length();

}

}

StringLengthComparator并不好,因为客户端无法传递任何其他的比较策略。我们应该定义一个接口,让StringLengthComparator实现这个接口

public interface Comparator<T> {

public int compare(T t1, T t2);

}

“宿主类”还可以导出公有的静态域,其类型为策略接口,具体的策略可以使宿主类的私有嵌套类

class Host {

private static class StrLenCmp implements Comparator<String>, Serializable {

public int compare(String s1, String s2) {

return s1.length - s2.length();

}

}

public static final Comparator<String> STRING_LENGTH_COMPARATOR = NEW StrLenCmp();

}

String类利用这种模式,通过它的CASE_INSENSITIVE_ORDER域,导出了一个不区分大小写的字符串比较器。

简而言之,函数指针的主要用途就是实现策略模式。为了在java中实现这种模式,要声明一个接口来表示策略,通过实现这个接口中的方法来定义自己的策略。

当具体的策略只被使用一次的时,建议使用匿名类类实例化这个策略。当被多次重复使用时,它的类通常就要被实现为私有的静态成员类,并通过公有的静态final域导出,其类型为个策略接口。

10、优先考虑静态成员类

嵌套类是指被定义在另一个类的内部的类。嵌套类存在的目的应该只是为它的外围类通过服务。

如果声明成员类不要求访问外围实例,就要始终把static修饰符放在它的声明中,使它成为静态成员类。如果省略了static修饰符,则每个实例都包含一个额外的指向外围对象的引用。

私有静态成员类的一种常见用法是用来代表外围类所代表的对象组件(Map)。

匿名类常见用法:一、动态的创建函数对象。二、创建过程对象(Runnable、Thread、TimerTask)。三、静态工厂方法的内部。

如果一个嵌套类需要在单个方法之外仍然是可见的,或者它太长,不适合放在方法内部,就应该使用成员类。如果成员类的每个实例都需要一个指向外围实例的引用,就该是非静态的;否则,就做成静态的。假设这个嵌套类属于一个方法的内部,如果你只需要在一个地方创建实例,并且已经有了一个预置的类型可以说明这个特征,就要把它做为匿名的;否则,就做成局部的。













  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值