美文网首页
要么为继承而设计,并提供文档说明,要么就禁止继承

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

作者: DZQANN | 来源:发表于2022-05-26 22:46 被阅读0次

    这一节讲的主要是理论,还是比较抽象

    对于专门为了继承而设计的类,需要具有良好的文档

    1. 该类的文档必须精确地描述覆盖每个方法所带来的影响

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

    3. 如果放大调用了可覆盖的方法,则应该使用@implSpec进行解释说明,以下例子来自AbstractCollection

      /**
           * {@inheritDoc}
           *
           * @implSpec
           * This implementation iterates over the collection looking for the
           * specified element.  If it finds the element,it removes the element
           * from the collection using the iterator's remove method.
           *
           * <p>Note that this implementation throws an
           * {@code UnsupportedOperationException} if the iterator returned by this
           * collection's iterator method does not implement the {@code remove}
           * method and this collection contains the specified object.
           *
           * @throws UnsupportedOperationException {@inheritDoc}
           * @throws ClassCastException            {@inheritDoc}
           * @throws NullPointerException          {@inheritDoc}
           */
          public boolean remove(Object o) {}
      

    类必须通过某种形式提供适当的钩子(hook)

    "钩子"这个词上次见是在《Head First设计模式》里面讲模板方法的地方。原文介绍是这样子的:钩子是一种被声明在抽象类中的方法,但是有空的或者默认的实现。钩子的存在,可以让子类用有能力对算法的不同点进行挂钩,要不要挂钩,要子类自行决定。

    举一个例子:

    public abstract class Father {
        public void publicMethod() {
            if (hook()) {
                privateMethod();
            }
        }
    
        protected boolean hook() {
            return false;
        }
    
        private void privateMethod() {
            
        }
    }
    
    public class Son extends Father {
        @Override
        protected boolean hook() {
            return true;
        }
    }
    

    JDK中有一个比较高级的用法,AbstractListclear方法会调用protectedremoveRange。然后ArrayList类重写了removeRange,使得remove方法的性能得到了提升

    对于钩子的使用,要适度,不然就会使代码结构变得非常复杂

    类必须通过某种形式提供适当的钩子(hook),以便能够进入到它的内部工作流程中,这种形式可以是精心选择的protected方法,也可以是protected的域,后者比较少见。

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

    1. 必须在发布之前先编写子类对类进行测试
    2. 经验表明,3个子类足以测试一个可扩展的类

    为了允许继承,类还必须遵守其他一些约束

    1. 构造器决不能调用可被覆盖的方法. 无论是直接调用还是间接调用.
      (因为超类的构造器在子类的构造器之前运行,如果子类中覆盖版本的方法依赖于子类构造器所执行的任何初始化工作,该方法将不会如预期般地执行.)

    2. 在为了继承而设计类的时候,CloneableSerializable接口出现了特殊的困难.
      无论是clone还是readObject,都和构造器类似,都不可以调用可覆盖的方法,不管是以直接还是间接的方式.
      如果该类有readResolvewriteReplace方法,就必须使它们成为受保护的方法.

    对于那些并非为了安全地进行子类化而设计和编写文档的类,要禁止子类化

    • 把类声明为final
    • 把所有的构造器都变成私有的,或者包级私有的,并增加一些公有的静态工厂来替代构造器

    思考

    1. 这一条的出发点更像是开发了底层框架供业务代码使用,我们的业务代码开发也很少会遇到像JDK的util包一样复杂的继承结构,所以里面的一些要求规范(比如说注释)可以不太严格的遵守
    2. 抽象类的编写中,还是应该减少可覆盖的方法(protected)的出现,尽量压缩方法,把方法压缩成privateabstract
    3. 对于"钩子"的使用,更像是为了一些特定的子类特殊逻辑而开放的"后门"。这种代码可以有,但是不能太多。当然如果想去掉这种代码也可以,比如上面的例子,可以把`privateMethod编程一个protected方法,然后默认没有内容,子类的重写添加特别的逻辑。但是这样的结果就是重写的范围被扩大了,如果程序变复杂,影响也会变大
    4. 我们很多的类都是Helper,Wrapper,本身也不会有人,也没有必要继承,加不加final意义不大

    相关文章

      网友评论

          本文标题:要么为继承而设计,并提供文档说明,要么就禁止继承

          本文链接:https://www.haomeiwen.com/subject/emqbprtx.html