第四章 类和接口(一)

类和接口是Java程序设计的核心,也是java语言的基本抽象单元

13.使类和成员的可访问性最小

   封装(信息隐藏原则):设计良好的模块和不好的模块最重要的区别是对于外部模块,是否隐藏内部数据和实现细节。设计良好的模块会隐藏所有的细节,只通过api进行通信。

   信息隐藏的好处:1.解除模块之间的耦合关系,使模块独立。

                   2.减轻维护负担,使程序员更快理解模块。

                   3.调节性能,增加系统重用性,降低构建大型系统风险。

   13.1信息隐藏的办法:访问控制。

      (1)尽可能的使每个成员或类不被外界访问。

          具体表现:protected和public 都是api的一部分,实例域不能公有;

14.在公有类中使用访问方法而非共有域

这一条很简单,就是尽量使用getter和setter方法来访问共有类中的数据,而不是让数据自己public。

     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条:使可变性最小化

不可变类:不可变类是其实例不能被修改的类。每个实例中包含的所有信息都必须在创建该实例的时候就提供,并在对象的整个生命周期内固定不变。Java中有许多不可变的类,比如String,BigInteger,BigDecimal。不可变类比可变类更易于设计,实现和使用,它们不容易出错,而且更加安全。

设计不可变类有下面5个原则:

1.不会提供任何会修改对象状态的方法。

2.保证类不会被扩展。(可以声明为final)

3.使所有域都是final的。

4.使所有的域都是私有的。这样可以防止客户端获得被访问域的引用的可变对象的权限,并防止客户端直接修改这些对象。

5.确保对于任何可变组件的互斥访问。如果类具有指向可变对象的域,则必须则必须确保该类的客户端无法获得指向这些对象的引用,并且,永远不要用客户端提供的对象引用来初始化这样的域,也不要从任何访问方法中返回该对象引用。

具体例子:

public final class Complex{

    private final double re;

    private final double im;

 

    public Complex(double re,double im){...}

    public double realPart(){return re;}

    public double imaginaryPart(){return im;}//这里是第一条,不会返回修改的方法

 

    public Complex add(Complex c){

        return new Complex(re+c.re,im+c.im);

    }

    public Complex subtract(Complex c){

   

        return new Complex(re-c.re,im-c.im);

    }

    ...

    @Override public boolean equals(Object o){...}

    @Override public int hashCode(){...}

@Override public String toString(){...}

}

这个类表示一个复数。这个类除了标准的Object 方法,还提供了实部和虚部的访问方法,以及四种基本运算:加减乘除。它们创建并返回新的实例,而不是修改当前实例,这种方法被称作functional方法,因为这些方法返回一个函数的结果,这些函数对操作数进行运算但并不修改它。

不可变对象本质上是线程安全的,他们不要求同步,所以,不可变对象可以被自由的共享。不仅可以共享不可变对象,甚至也可以共享它们的内部信息,如BigInteger类内部使用的符号数值表示法。符号用一个int 类型的值表示,数值用一个int数组表示。Negate方法产生一个新的BigInteger,其中数值一样,符号相反。新的BigInteger不需要拷贝数组,也指向原始实例中的同一个内部数组。

不可变类使用扩展:提供静态工厂,把频繁请求的实例缓存起来,使客户端可以共享,降低内存占用和垃圾回收成本。

扩展:1.使类不被子类化的另一个更灵活的方法,让类的所有构造器变成私有或者包级私有,并添加公有的静态工厂。

具体实例:

public class Complex{

    private final double re;

    private final double im;

 

    private Complex(double re,double im){...}

  

    public static Complex valueOf(double re,double im){

      return new Complex(re,im);

}

    ...

  

}

这种方法可以避免构造器凌乱,更具可读性。

2.不可变类第4条,所有的域必须是final的,在真正实行的时候,为了性能可以放松一点。实际是没有一个方法能够对对象的状态产生外部可见的改变。许多不可变的类拥有一个或多个非final的域,在第一次被请求执行计算的时候,把一些昂贵的计算结果缓存在这些域中。如果下次再与遇到同样的计算,直接返回缓存的值。因为对象是不可变的,它的不可变性保证了这些计算如果被再次执行,会产生同样的结果。如String类的hashCode方法。

16.复合优先于继承

与方法调用不同的是,继承打破了封装性,换句话说,子类依赖于父类中某些功能的实现细节。父类的实现可能因为版本的变化有所变化,那么子类的功能可能会被破坏,即使它的代码并没有改变。

复合是不扩展现有的类,而是在新的类中增加一个私有域,它引用现有类的一个实例。现有的类变成了新类的一个组件。

复合中,新类中的每个实例方法都可以调用被包含的现有类实例中对应的方法,并返回它的结果,这被称为“转发”。具体实例:

ForwardingSet实现了Set接口,InstrumentedSet继承了ForwardingSet,增加了一个私有域addCount,不仅可以对HashSet计数,还可以对实现了Set接口的其他Set实例计数。

因为每一个InstrumentedSet都把另一个Set实例包装起来了,所以InstrumentedSet为包装类。这种模式叫做装饰模式

继承可能导致的问题:1.子类中的函数调用了父类的super.f(),而父类的f()中其实又调用了父类的f2()函数,这种“自用性”是实现细节,不是承诺,不能保证Java平台的所有实现都不变,可能会因为版本不同而发生改变。

2.如果在子类中添加了一个父类中没有的类,但是不巧,在下一版本中父类也有了同名的类,那么可能会变成一个覆盖方法,或重载方法,又或者,子类中的这个方法无法遵守父类中方法的约定。

如果在适合用复合的地方使用了继承,则会暴露实现细节,这样得到的API会把你限制在原始的实现上,永远限定了类的性能。更严重的是,由于暴露了内部细节,客户端有可能就直接访问这些细节,会导致语义上的混淆,甚至直接修改超类,从而破坏子类的约束条件。

在决定使用继承而不是符合之前,还应问自己最后一组问题:对于你试图扩展的类,它的AI中有没有缺陷?如果有,你是否愿意把那些缺陷传播到类的API中?

总之,只有确实是is a 关系时,才应该使用继承。

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

对于为了继承设计的类,文档必须全面:

1.文档必须精确描述覆盖每个方法所带来的影响。对于每个公有的或者受保护的方法或构造器,文档必须指明该方法调用了哪些可覆盖的方法,是以什么顺序调用的,每个调用的结果又是如何影响后续的处理过程的。

2.必须在文档中说明,哪些情况下它会调用可覆盖的方法。

对于为了继承而设计的类,唯一的测试方法就是编写子类。

参考资料

https://www.cnblogs.com/cangyikeguiyuan/p/4415507.html

《Effective java》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值