EffectiveJava——类与接口2

第15条:使可变性最小化

不可变类只是一个实例不能给修改的类,在每个对象创建的时候,所有的信息都要提供,信息一旦确定,在对象的真个生命周期都不可改变,java中有很多的不可变类:String,基本数据类型包装类,BigInteger,BigDecimal,不可变类有如下优点:更加易于设计,实现和使用,不容易出错,更加安全

使类成为不可变类要遵守以下规则:
- 不要提供人任何修改对象状态的方法
- 保证类不可被拓展,比如final类
- 使所有域都是final的
- 使所有域都变成私有的
- 确保对于任何可变组件的互斥访问。永远不要用客户端提供的对象来初始化对象的私有可变域,使用保护性拷贝。

不可变象的好处

不可变对象本质上是线程安全的,他们不需要同步。
不可变对象可以被自由的共享。
推荐使用静态final常量表示频繁用到的不可不对象。
不可变对象不仅可以被共享,甚至可以共享他们的内部信息

不可变象真正唯一的缺点是,对于每一个值都需要一个单独的对象

技巧

如果需要执行一个多步骤的操作,且每一个操作都会产生一个新的对象,但是只有最后的结果对象才被需要,这样会有性能问题,解决方法是提供一个可变配套类,如String对应的StringBuilder。

一种更加灵活的让不可变类变成final的办法是,让类的所有构造器变为私有的或者包级私有的,并添加静态工厂方法代替共有的构造器。这样不可变类便有了对象缓存能力。

注意

如果选择让不可变类实现Serializable接口,并且包含多个可变对象域,就必须显示的提供readObject和readResolve方法,

总之:坚决不要给每一个get方法编写一个对应的set方法,除非有很好的理由,要让类变成可变类,否则就应该是不可变的,如果类不能做到不可变的,仍然应该尽可能的控制它的可变性,降低对象可以存在的状态数,可以更容易分析对象的行为

第16条:复合优先于继承

继承(inheritance)是实现代码重用的重要手段,但是使用不当,会导致软件变得很脆弱。在包类使用继承是非常安全的,这里的类都处于同一个程序员控制之下,然而对于普通的类进行跨包边界进行继承是非常危险的,

与方法调用不同的是,继承打破了封装性,子类依赖于超类中特定功能的实现细节,但是超类的实现可能随着版本发行而改变,如果真的改变了,那么子类可能遭到破坏,即使它的代码完全没有改变。

总之就不受控的情况下,超类随着发行版本的某些实现改变,对给子类的危险是不可预知的。

使用复合(composition)

不扩展现有的类,而是在新的类中增加一个私有域,它引用现有类的一个实例,这种设计承做复合。
在新的类中每个实例方法都可以调用被包含的现有类实例中对应的方法,并返回它的结果,这称之为转发。这样得到的类将更加稳固。它不依赖于现有类的实现细节,即使现有类添加了新的方法,也不会影响新的类。

这也应该是一种包装设计模式,在java Api中就有很多的体现,比如Java中的IO大量使用了包装设计模式,而不是继承。

public class FilterInputStream extends InputStream {
    protected FilterInputStream(InputStream in) {
        this.in = in;
    }
......}

实现这种模式通常的步骤是:
1. 实现被包装类的共有接口
2. 在构造方法声明共有接口,以此来注入被包装类
3. 实现方法时,调用被保证类对于的方法

接口给复合带来了格外的灵活性,通过接口,我们可以包装任何接口的实现。

总结:
只有当子类真正是超类的子类型时,才适合继承,在决定使用继承而不是复合之前,还需要考虑被扩展的类的API是否有缺陷,继承机制会把超类所有的API的缺陷传播到子类中,而复合则允许设计新的API来隐藏这些缺陷。继承很强大,但是它违背了封装的原则,包装类不仅比子类更加健壮,而且功能更加强大。

你可能感兴趣的:(java)