代码复用的几个级别:
- 源代码级别的复用
- 模块级别的复用(类/抽象类/接口)
- 库级别的复用(API)
- 系统级别的复用:框架
白盒复用:源代码可见、可扩展、可修改
黑盒复用:源代码不可见,只可调用API
找源代码的几个网站:
grepcode.com
github.com
searchcode.com
本文主要介绍模块级别的复用——类/接口
复用一个类的方式——继承、委托
继承
继承时,子类将继承父类的所有功能。
子类可以override父类的功能,也可以在父类的基础上,增加新的功能。
在实现继承类之前,最好先设计好继承层次图。
子类不可以丢弃父类的属性或方法,因此在设计继承结构时务必特别小心。
委托(delegation)
委托用于:一个类仅依赖于另一个类的部分功能模块时,例如,Sorter就将一部分功能委托给了Comparator。
显式的委托:把被委托类直接传给委托类
隐式的委托:by the member lookup rules of the language
委托是一种比较低级的共享代码机制。
库级别的复用(API/Package)
库(Library)是一些具有可重用功能的类的集合。
框架(Framework)是一组可以根据应用需求定制的代码骨架。
系统级别的复用(Framework)
框架是一组抽象类、具体类及其链接关系,只有骨架,没有血肉。
开发者根据自己的具体应用需求,在框架中填入代码,形成完整的系统。
白盒框架:通过代码层面的继承来进行框架扩展
黑盒框架:通过实现特定的接口来完成代码复用
LSP可替换原则
行为子类型
- 子类型可以增加方法,但不能删除方法。
- 子类型需要实现抽象类型中所有未实现的方法。
- 子类型方法的返回值类型只能与原方法的返回值类型相同或是其子类型。
override一个方法时,设父类方法返回值类型是T1,子类方法返回值类型是T2,则T2必须与T1相同,或T2是T1的子类型。
原因如下,在实现多态时,可能有一个父类对象,用子类型实例化,就可能用一个T1类型的变量用于接收父类方法的返回值,这样在编译时才能通过。那么在runtime,执行的实际上是子类方法,如果子类方法返回的是T1的父类型,则无法被T1类型的变量接收;子类方法如果返回的是T1的子类型,则可以被T1类型的变量接收,这也是一个多态的过程。因此,要求T2是T1的子类型。 - 子类型方法的参数必须是与原方法参数类型相同或是其父类型(这种情况在Java中按overload处理)
override一个方法时,设父类方法中参数类型是T1,子类方法参数类型是T2,则T2必须与T1相同,或是T1的父类型,原因如下。
在实现多态时,可能有一个父类类型的对象,用子类类型实例化。在调用这个方法时,传递的参数,必须是T1或T1的子类型,这样编译才能通过。而在运行时,实际上调用的是子类的方法,如果子类方法要求的参数类型T2是T1的子类型,而父类传入了T1类型,则在运行时,子类方法的参数无法用T2来接收一个T1类型的对象,就会出错。因此,要求子类方法接收的参数类型必须是T1或T1的父类型,这样才能适配多态机制。而在Java中,这样的机制不是override,而是overload。 - 子类型方法抛出的异常必须少于或等于父类方法抛出的异常
这个很容易理解,在调用父类方法,只处理了一部分异常使得编译时能通过。而运行时实际上是执行的子类方法,如果抛出了额外的异常,就会导致运行时出现未处理的异常。因此子类方法抛出的异常只能变少不能变多。 - 子类型的规约必须比父类型规约更强,或相等。
这就意味着子类型拥有更强的不变量、更弱的前置条件、更强的后置条件,因为要求父类接收的输入,子类必须也能够接收;子类给出的返回,父类必须也能够处理。这就要求子类接收的输入范围更大、子类给出的输出范围更小。
本文探讨了代码复用的四个级别:源代码、模块、库和系统,并重点介绍了模块级别的复用,如类和接口的使用。通过继承和委托实现类的复用,同时讲解了API和框架的概念。此外,还阐述了白盒和黑盒复用的区别。文章还强调了Liskov替换原则在多态中的重要性,确保子类型行为的正确性。

被折叠的 条评论
为什么被折叠?



