composition VS. inheritance
1. 区别概括:
Composition:
*. have-a,
*. 复用了old class的功能:
*. 不想Upcasting.
*. summary: 一般来说,合成用于新类要使用旧类的功能,而不是其接口的场合。也就
是说,把对象嵌进去,用它来实现新类的功能,但是用户看到的是新类的
接口,而不是嵌进去的对象的接口。因此,你得在新类里嵌入private
的旧类对象。)
Inheritance:
*. is-a,
*. 复用了old class的接口,
*. 想Upcasting
*. summary: 继承则是要对已有的类做一番改造,以此获得一个特殊版本。简而言之,
你要将一个较为抽象的类改造成能适用于某些特定需求的类。稍微想一下
就会知道,用vehicle(车辆)对象来合成一个car(轿车)是毫无意义的——car 不包含vehicle,
它本来就是vehicle。继承要表达的是一种“是(is-a)”关系,而合成表达要表达的是“有(has-a)”关系。
2. 合成还是继承,再探讨:
在面象对象的编程中,最常见的编程和使用代码的方式还是将数据和方法
简单地封装成类,然后再使用那个类的对象。你也可以通过合成,在现有
的类的基础上创建新的类。继承则不太常用。所以,虽然在OOP 的学习
中,继承占有很重要的地位,但这并不是在说你可以到处滥用。相反,运
用继承的时候,你应该尽可能的保守,只有在它能带来很明显的好处的时
候,你才能用。在判断该使用合成还是继承的时候,有一个最简单的办
法,就是问一下你是不是会把新类上传给基类。如果你必须上传,那么继
承就是必须的,如果不需要上传,那么就该再看看是不是应该用继承了。
下一章(多态性)会讲为什么要用上传,但是如果你还记得要问自己<b>“我需
要上传吗?”</b>,那么你就有了一件能帮你判断该使用合成还是继承的好工
具了。
3. 总结:
继承和合成都能让你在已有的类的基础上创建新的类。但是通常情况下,
合成是把已有的类当作新类底层实现的一部分来复用,而继承则是复用其
接口。由于派生类拥有基类的接口,因此它可以被上传(upcast)到基类,
尽管面向对象的编程会反复强调继承,但是当你着手设计的时候,通常情
况下还是应该先考虑合成,只有在必要的时候才使用继承。合成会更灵
活。此外,还可以让成员使用继承类的对象,这样你就能在运行时更换这
些成员的具体类型,及其行为了。于是,合成后的对象的行为方式也能得
以改变了。
所有非primitive对象都有一个toString( )方法,当编译器需要一个String而它却是一个对象的时候,
编译器就会自动调用这个方法。
"" + x 可以自动使x转换为String(如果x为非primitive对象,那么就会调用toString方法)
单元测试
你可以为每个类都创建一个main( ),而且这也是一种值得提倡的编程方法,因为这样一来,测试代码就能都放进类里了。
即使程序包括了很多类,它也只会调用你在命令行下给出的那个类的main( )方法。
( 只要main()是public 的就行了,至于类是不是public 的,并不重要。)
这种往每个类里都放一个main( )的做法,能让类的单元测试变得更容易一些。
做完测试之后,你也不必移除main( );留下它可以供以后的测试用。
继承设计方面有一条通用准则,那就是把数据都设成private 的,把方法都设成public 的。
继承中应该用private还是protected?
最好的做法是,将<b>数据成员(fields)设成private 的</b>,
然后<b>用protected权限的method</b>来控制继承类的访问权限:
新类的对象(this)包含了base class的subObject (super):
从局外人的角度来看,新类具备了和旧类完全相同的接口,并且还有可能会有
一些它自己的方法和数据。但继承并不仅仅是拷贝基类的接口。
当你创建一个派生类对象的时候,这个对象里面还有一个基类的子对象(subobject)。
这个子对象同基类自己创建的对象没什么两样。
只是从外面看来,这个子对象被包裹在派生类的对象里面。
编译器会强制你将基类构造函数的调用--super(...), 放在派生类的构造函
数的最前面。也就是说,在它之前不能有任何东西。
try {
...
} finally {
...
}
try 表示下面这段程序(由花括号限定)是一个需要给予特殊关注的受保护的区域(guarded region)。
所谓的特殊关注就是,无论以何种方式退出try 区块,都必须执行跟在这个受保护区域后面的finally子句。
(对于异常处理来说,会有很多非正常退出try 区块的情况。)这里
在很多情况下,清理并不是什么问题;把它留给垃圾回收器就行了。但是
如果你要自己做的话,那就只能辛苦一点了,而且还要格外小心,因为在
垃圾回收方面,谁都帮不上你。垃圾回收器可能永远也不会启动。即便它
启动了,你也没法控制它的回收顺序。<b>最好不要依赖垃圾回收器去做任何
与内存回收无关的事情。</b>如果你要进行清理,一定要自己写清理方法,别
去用finalize( )。
渐进式的开发
继承的优点之一就是,它支持渐进式的开发(incremental develop)。
添加新的代码的时候,不会给老代码带来bug;实际上新的bug 全都被圈在新代码里。
通过继承已有的,已经能正常工作的类,然后再添加一些fields和method(以及重新定义已有的method),
你可以不去修改那些可能还有人在用的老代码,因而也就不会造成bug 了。一旦发现了bug,你
就知道它肯定是在新代码里。相比要去修改老代码,新代码会短很多,读起来也更简单。
尽管在试验阶段,继承是一种非常有用的技术,但是当项目进入稳定阶段
之后,你就得用一种新的眼光来审视类的继承体系了,你要把它压缩成一
个合乎逻辑的结构。记住,在这些错综复杂的关系后面,继承实质上是在
表达这样一种关系:“This new class is a type of that old class.(新的类是一种旧的类)”。
程序不应该围着bits 转,它应该从问题空间出发,通过创建和操控形形色色的对象来表达一种解决
问题的方法。
上传(Upcasting)
继承最重要的特征不在于它为新类提供了method,而是它表达了新类同基类
之间的关系。这种关系可以被归纳为一句话
“The new class is a type of the existing class (新类就是一种原有的类)。”
继承的意思就是基类有的方法派生类都有,也就是说派生类是基类的超集(superset)。
因此送给基类的消息也可以送给派生类。
final
fields:
1.primitive: 值不能修改
2.非primitive (object reference): 不能指向其他对象
method:
1.禁止overriding
2.效率(用处不大)
class:
防止被继承。final class的fields可以是final也可以不是,
但method都隐含是final(因为class不能被继承,所以method也不能被复写)
ArrayList替代了Vector
HashMap替代了HashTable
override: derived class 重定义了一个base class同名且参数列表相同非private的method.
overload: 不管是在derived class还是在base class, 同名method只要参数列表不同,则都是可见的,不会被覆写(override)掉
注意:只有基类接口里(Private method不属于接口)的东西才能被覆写(overriding)
如果方法是private 的,那它就不属于基类的接口。它只能算是被类隐藏起来的,正好有着相同的名字的代码。
如果你在派生类里创建了同名的public 或protected,或package 权限的方法,那么它们同基类中可
能同名的方法,没有任何联系。你并没有覆写那个方法,你只是创建了一个新的方法。
不要的陷入过早优化代码的陷阱。假如你有了一个能正常工作但是运行速度
很慢的系统,那能不能用final 解决问题还是件很难说的事。
不过我们可以通过profiling这是一个能帮你改善程序运行速度的工具。
本文地址:http://com.8s8s.com/it/it12993.htm