目录
- 使类和成员的可访问性最小化
- 在公共类中使用访问方法而不是公共属性
- 最小化可变性
- 组合优于继承
- 如果使用继承则设计,并文档说明,否则不该使用
- 接口优于抽象类
- 为后代设计接口
- 接口仅用来定义类型
- 优先使用类层次而不是标签类
- 优先考虑静态成员类
- 将源文件限制为单个顶级类
类和接口
使类和成员的可访问性最小化
- 经验法则很简单:让每个类或成员尽可能地不可访问
- 除了作为常量的公共静态final属性之外,公共类不应该有公共属性。 确保public static final属性引用的对象是不可变的。
- 如果一个类只在本包中访问,不需要被外包访问,则直接声明成class即可,而如果一个类需要被外包访问,则必须声明成public class。
在公共类中使用访问方法而不是公共属性
// 错误eg
class Point {
public double x;
public double y;
}
// 正确eg
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; }
}
- 由于这些类的数据属性可以直接被访问,因此这些类不提供封装的好处(条目 15)。 如果不更改API,则无法更改其表示形式,无法强制执行不变量,并且在访问属性时无法执行辅助操作。 坚持面向对象的程序员觉得这样的类是厌恶的,应该被具有私有属性和公共访问方法的类
最小化可变性
- 不可变对象很简单。 一个不可变的对象可以完全处于一种状态,也就是被创建时的状态。不可变对象没有多线程问题。
- 不可变类的主要缺点是对于每个不同的值都需要一个单独的对象
- 如果一个类不能设计为不可变类,那么也要尽可能地限制它的可变性。
- 不可变类应该遵循
- 不要提供修改对象状态的方法
- 确保这个类不能被继承
- 把所有属性设置为final,private
- 确保对任何可变组件的互斥访问(返回new对象)
- 有关序列化,如果让自己的不可变类实现序列化,就必须显式提供 readObject 或者 readResolve,否则反序列化可能会产生新的实例
public final class Complex {
private final double re;
private final double im;
private Complex(double re, double im) {
this.re = re;
this.im = im;
}
// 将构造函数改为私有的,并添加静态工厂来替代公有构造器
public static Complex valueOf(double re, double im) {
return new Complex(re, im);
}
public double realPart() {
return re;
}
public double imaginaryPart() {
return im;
}
public Complex plus(Complex c) {
return new Complex(re + c.re, im + c.im);
}
public Complex minus(Complex c) {
return new Complex(re - c.re, im - c.im);
}
public Complex times(Complex c) {
return new Complex(re * c.re - im * c.im,
re * c.im + im * c.re);
}
public Complex dividedBy(Complex c) {
double tmp = c.re * c.re + c.im * c.im;
return new Complex((re * c.re + im * c.im) / tmp,
(im * c.re - re * c.im) / tmp);
}
@Override
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (!(o instanceof Complex)) {
return false;
}
Complex c = (Complex) o;
// See page 47 to find out why we use compare instead of ==
return Double.compare(c.re, re) == 0
&& Double.compare(c.im, im) == 0;
}
@Override
public int hashCode() {
return 31 * Double.hashCode(re) + Double.hashCode(im);
}
@Override
public String toString() {
return "(" + re + " + " + im + "i)";
}
}
- 不可变类的缺点
- 在特定的情况下,存在潜在的性能问题,比如执行一个多步骤操作,每个步骤都会产生一个新的对,但除了最后的结果之外其他的对象最终都会被丢弃,就会有性能问题
- 所以应该使一些小的值对象成为不可变的
- 如果发生了性能问题,才应该为不可变的类提供公有的可变配套版
组合优于继承
- 继承是强大的,但它是有问题的,因为它违反封装。 只有在子类和父类之间存在真正的子类型关系时才适用。 即使如此,如果子类与父类不在同一个包中,并且父类不是为继承而设计的,继承可能会导致脆弱性。 为了避免这种脆弱性,使用合成和转发代替继承,特别是如果存在一个合适的接口来实现包装类。 包装类不仅比子类更健壮,而且更强大。
- 以下这段代码统计次数会出问题,addAll会调用add方法所以被重复加了,因为HashSet的addAll会掉add方法
public class InstrumentedHashSet<E> extends HashSet<E> {
// The number of attempted element insertions
private int addCount = 0;
public InstrumentedHashSet() {
}
public InstrumentedHashSet(int initCap, float loadFactor) {
super(initCap, loadFactor);
}
@Override public boolean add(E e) {
addCount++;
return super.add(e);
}
@Override public boolean addAll(Collection<? extends E> c) {
addCount += c.size();
return super.addAll(c);
}
public int getAddCount() {
return addCount;
}
}
- 用组合则没问题, 因为Set的addAll调用add方法并没有被覆盖
public class ForwardingSet<E> implements Set<E> {
private final Set<E> s;
public ForwardingSet(Set<E> s) {
this.s = s;
}
public void clear() {
s.clear();
}
public boolean contains(Object o) {
return s.contains(o);
}
public boolean isEmpty() {
return s.isEmpty();
}
public int size() {
return s.size();
}
public Iterator<E> iterator() {
return s.iterator();
}
public boolean add(E e) {
return s.add(e);
}
public boolean remove(Object o) {
return s.remove(o);
}
public boolean containsAll(Collection<?> c) {
return s.containsAll(c);
}
public boolean addAll(Collection<? extends E> c) {
return s.addAll(c);
}
public boolean removeAll(Collection<?> c) {
return s.removeAll(c);
}
public boolean retainAll(Collection<?> c) {
return s.retainAll(c);
}
public Object[] toArray() {
return s.toArray();
}
public <T> T[] toArray(T[] a) {
return s.toArray(a);
}
@Override
public boolean equals(Object o) {
return s.equals(o);
}
@Override
public int hashCode() {
return s.hashCode();
}
@Override
public String toString() {
return s.toString();
}
}
public class InstrumentedSet<E> extends ForwardingSet<E> {
private int addCount = 0;
public InstrumentedSet(Set<E> s) {
super(s);
}
@Override public boolean add(E e) {
addCount++;
return super.add(e);
}
@Override public boolean addAll(Collection<? extends E> c) {
addCount += c.size();
return super.addAll(c);
}
public int getAddCount() {
return addCount;
}
}
如果使用继承则设计,并文档说明,否则不该使用
接口优于抽象类
- 要实现由抽象类定义的类型,类必须是抽象类的子类。 因为Java只允许单一继承,所以对抽象类的这种限制严格限制了它们作为类型定义的使用
为后代设计接口
- 许多新的默认方法被添加到Java 8的核心集合接口中,主要是为了方便使用lambda表达式
- 接口加默认方法设计初衷不是为了新增方法用
接口仅用来定义类型
- 接口只能用于定义类型。 它们不应该仅用于导出常量。
优先使用类层次而不是标签类
优先考虑静态成员类
- 如果嵌套类的实例可以与其宿主类的实例隔离存在,那么嵌套类必须是静态成员类
- 集合接口(如Set和List)的实现通常使用非静态成员类来实现它们的迭代器
将源文件限制为单个顶级类
- 源文件有多个顶级类,可能不同源文件会有重复
- 如果试图将多个顶级类放入单个源文件中,请考虑使用静态成员类(条目 24)作为将类拆分为单独的源文件的替代方法
public class Test {
public static void main(String[] args) {
System.out.println(Utensil.NAME + [Dessert.NAME](http://Dessert.NAME));
}
private static class Utensil {
static final String NAME = "pan";
}
private static class Dessert {
static final String NAME = "cake";
}
}
参考文章
网友评论