OOP组合和继续的优缺点
—— 详解继承与组合的优缺点

组合与继承都是提高代码可重用性的手段。在设计对象模型时,可以按照语义来识别类之间的组合关系和继承关系。在有些情况下,采用组合关系或者继承关系能完成同样的任务,组合和继承存在着对应关系:组合中的整体类和继承中的子类对应,组合中的局部类和继承中的父类对应,如下图:
组合:
继承:
一、基础知识
我们先用代码帮大家来理解一下组合和继承:
1、对于已经存在Parent类时需要扩展其方法时
结构图:

继承代码:
代码 class Parent
{
public void Method1()
{
}
public void Method2() { }
public void Method3() { }
}
class Child1:Parent
{
public void MethodA() { }
}
class Child2:Parent
{
public void MethodA() { }
}
组合代码:
代码 class ComponentA
{
Parent p = new Parent();
public void Method2()
{
p.Method2();
}
public void MethodA()
{
}
}
class ComponentB
{
Parent p = new Parent();
public void Method2() {
p.Method2();
}
public void MethodB()
{
}
}
2、如果发现两个类具有很多代码相同的类需要抽象时,如下图A,B两个类,这两个类中method1,和method3两个方法代码相同

继承:

实现代码:
代码 class A:C
{
public void MethodB() { }
public override void Method2()
{
}
}
class B:C
{
public override void Method2()
{
}
public void MethodB() { }
}
class C
{
public void Method1() { }
public virtual void Method2() { }
public void Method3() { }
}
组合:

实现代码:
代码 class A
{
public void MethodA() { }
C c = new C();
public void Method1()
{
c.Method1();
}
public void Method2() { } }
class B
{
public void MethodB() { }
public void Method2() { }
}
class C
{
public void Method1() { }
public void Method2() { }
public void Method3() { }
}
二、继承与组合的优缺点
|
合 关 系 |
继 承 关 系 |
|
优点:不破坏封装,整体类与局部类之间松耦合,彼此相对独立 |
缺点:破坏封装,子类与父类之间紧密耦合,子类依赖于父类的实现,子类缺乏独立性 |
|
优点:具有较好的可扩展性 |
缺点:支持扩展,但是往往以增加系统结构的复杂度为代价 |
|
优点:支持动态组合。在运行时,整体对象可以选择不同类型的局部对象 |
缺点:不支持动态继承。在运行时,子类无法选择不同的父类 |
|
优点:整体类可以对局部类进行包装,封装局部类的接口,提供新的接口 |
缺点:子类不能改变父类的接口 |
|
缺点:整体类不能自动获得和局部类同样的接口 |
优点:子类能自动继承父类的接口 |
|
缺点:创建整体类的对象时,需要创建所有局部类的对象 |
优点:创建子类的对象时,无须创建父类的对象 |
1、为什么继承破坏封装性:

鸭子中不想要“飞”的方法,但因为继承无法封装这个无用的“飞”方法 。
2、为什么继承紧耦合:

当作为父类的BaseTable中感觉Insert这个名字不合适时,如果希望将其修改成Create方法,那使用了子类对象Insert方法将会编译出错,可能你会觉得这改起来还算容易,因为有重构工具一下子就好了并且编译错误改起来很容易。但如果BaseTable和子类在不同的程序集中,维护的人员不同,BaseTable程序集升级,那本来能用的代码忽然不能用了,这还是很难让人接受的
3、为什么继承扩展起来比较复杂

当图书和数码的算税方式和数码产品一样时,而消费类产品的算税方式是另一样时,如果采用继承方案可能会演变成如下方式:

这样如果产品继续增加,算税方式继续增加,那继承的层次会非常复杂,而且很难控制,而使用组合就能很好的解决这个问题,参见:面向对象思想的头脑风暴(一)
4、继承不能支持动态继承
这个其实很好理解,因为继承是编译期就决定下来的,无法在运行时改变,如3例中,如果用户需要根据当地的情况选择计税方式,使用继承就解决不了,而使用组合结合反射就能很好的解决。
5、为什么继承 子类不能改变父类接口
如2中的图
子类中觉得Insert方法不合适,希望使用Create方法,因为继承的原因无法改变
三、如何使用继承
1、精心设计专门用于被继承的类,继承树的抽象层应该比较稳定,一般不要多于三层。
2、对于不是专门用于被继承的类,禁止其被继承。
3、优先考虑用组合关系来提高代码的可重用性。
4、子类是一种特殊的类型,而不只是父类的一个角色
5、子类扩展,而不是覆盖或者使父类的功能失效
四、组合的缺点:
1、整体类不能自动获得和局部类同样的接口
如果父类的方法子类中几乎都要暴露出去,这时可能会觉得使用组合很不方便,使用继承似乎更简单方便。但从另一个角度讲,实际上也许子类中并不需要暴露这些方法,客户端组合应用就可以了。所以上边推荐不要继承那些不是为了继承而设计的类,一般为了继承而设计的类都是抽象类。
2、创建整体类的对象时,需要创建所有局部类的对象
这个可能没什么更好的办法,但在实际应用中并没有多出多少代码。
五、相关原则
1、里氏代换原则(LSP)(以下转自http://www.cnblogs.com/zhenyulu/articles/36061.html)
Liskov Substitution Principle(里氏代换原则):子类型(subtype)必须能够替换它们的基类型。
白马、黑马

反过来的代换不成立
《墨子·小取》说:"娣,美人也,爱娣,非爱美人也……"娣便是妹妹,哥哥喜爱妹妹,是因为两人是兄妹关系,而不是因为妹妹是个美人。因此,喜爱妹妹不等同于喜爱美人。用面向对象语言描述,美人是基类,妹妹是美人的子类。哥哥作为一个有"喜爱()"方法,接受妹妹作为参数。那么,这个"喜爱()"方法一般不能接受美人的实例。

下边那个长方形正方形的例子我就不转了,大家可以到上边那个博客地址中了解。
2、合成/聚合复用原则(CARP)(以下转自http://www.cnblogs.com/zhenyulu/articles/36068.html)
合成/聚合复用原则(Composite/Aggregate Reuse Principle或CARP)经常又叫做合成复用原则(Composite Reuse Principle或CRP),就是在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分;新对象通过向这些对象的委派达到复用已有功能的目的。
简而言之,要尽量使用合成/聚合,尽量不要使用继承。
o Design to interfaces.
o Favor composition over inheritance.
o Find what varies and encapsulate it.
(摘自:Design Patterns Explained)
区分"Has-A"与"Is-A"
"Is-A"是严格的分类学意义上定义,意思是一个类是另一个类的"一种"。而"Has-A"则不同,它表示某一个角色具有某一项责任。
导致错误的使用继承而不是合成/聚合的一个常见的原因是错误的把"Has-A"当作"Is-A"。
例如:

实际上,雇员、经理、学生描述的是一种角色,比如一个人是"经理"必然是"雇员",另外一个人可能是"学生雇员",在上面的设计中,一个人无法同时拥有多个角色,是"雇员"就不能再是"学生"了,这显然是不合理的。
错误源于把"角色"的等级结构与"人"的等级结构混淆起来,误把"Has-A"当作"Is-A"。解决办法:

总结:
根据我们前面讲的内容我们可以发现继承的缺点远远多于优点,尽管继承在学习OOP的过程中得到了大量的强调,但并不意味着应该尽可能地到处使用它。相反,使用它时要特别慎重。只有在清楚知道继承在所有方法中最有效的前提下,才可考虑它。 继承最大的优点就是扩展简单,但大多数缺点都很致命,但是因为这个扩展简单的优点太明显了,很多人并不深入思考,所以造成了太多问题,希望这篇文章能引发你一些思考。
OOP组合和继续的优缺点的更多相关文章
- 又一次认识java(四) — 组合、聚合与继承的爱恨情仇
有人学了继承,认为他是面向对象特点之中的一个,就在全部能用到继承的地方使用继承,而不考虑到底该不该使用,无疑.这是错误的.那么.到底该怎样使用继承呢? java中类与类之间的关系 大部分的刚開始学习的 ...
- 面向对象程序设计(OOP设计模式)-结构型模式之装饰器模式的应用与实现
课程名称:程序设计方法学 实验4:OOP设计模式-结构型模式的应用与实现 时间:2015年11月18日星期三,第3.4节 地点:理1#208 一.实验目的 加深对结构型设计模式的理解以及在开发中的实际 ...
- 我所理解的OOP——UML六种关系
最近由于经常给公司的小伙伴儿们讲一些OOP的基本东西,每次草纸都被我弄的很尴尬,画来画去自己都乱了,有时候也会尝试使用UML表示类之间的关系,但UML从毕业后就再也没接触过了,经常会被小伙伴儿们指出继 ...
- Python2.6-原理之类和oop(下)
来自<python学习手册第四版>第六部分 五.运算符重载(29章) 这部分深入介绍更多的细节并看一些常用的重载方法,虽然不会展示每种可用的运算符重载方法,但是这里给出的代码也足够覆盖py ...
- oop、configparser、xml模块
本节大纲:一:在python中,有两种编程思想.1:函数式编程.2:oop.无论是函数式编程还是oop都是相辅相成.并不是说oop比函数式编程就好.各有各的优缺点.在其他语言中java等只能以面向对象 ...
- 面向对象编程(OOP)基础之UML基础
在我们学习OOP过程中,难免会见到一些结构图~各种小框框.各种箭头.今天小猪就来简单介绍一下这些框框箭头的意思——UML. UML定义的关系主要有:泛化(继承).实现.依赖.关联.聚合.组合,这六种关 ...
- 面试题_103_to_124_关于 OOP 和设计模式的面试题
这部分包含 Java 面试过程中关于 SOLID 的设计原则,OOP 基础,如类,对象,接口,继承,多态,封装,抽象以及更高级的一些概念,如组合.聚合及关联.也包含了 GOF 设计模式的问题. 103 ...
- [GeekBand] C++学习笔记(2)——BigThree、OOP
本篇笔记主要分为三个部分,第一部分是以String类为例的基于对象的编程,重点在于构造与析构.拷贝构造函数.拷贝赋值函数三个重要函数.这一部分与笔记(1)中的内容结合起来就是基于对象编程的主要内容.第 ...
- (转)我所理解的OOP——UML六种关系
原文地址:http://www.cnblogs.com/dolphinX/p/3296681.html 最近由于经常给公司的小伙伴儿们讲一些OOP的基本东西,每次草纸都被我弄的很尴尬,画来画去自己都乱 ...
随机推荐
- 用 oracle vitual box 克隆虚拟机,找不到eth0的解决方案
用 oracle vitual box 克隆虚拟机 当我们需要使用多台虚拟机的时候,如果一台一台的安装,实在是太过麻烦了.所以一般的虚拟机软件都为我们提供了克隆已有虚拟机状态的功能.Oracle vi ...
- Android系统Surface机制的SurfaceFlinger服务的启动过程分析
文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/8022957 在前面一篇文章中,我们简要介绍了A ...
- C# Socket学习笔记二
小记:昨天咱们已经了解了Socket的通信原理,可是点对点的一次通信并不是我们想要的,那么今天那我们就继续学习异步通信,简单来说就是服务器端和客户端可以进行多次 互发信息的通信而不用担心通道会关闭.在 ...
- 让资源可以下载a
第一种方式------不存在任何兼容性 <a href='x.zip'>下载</a> 将要链接的资源进行打包即可 第二种方式----存在兼容性,目前只有Chrome 和Fire ...
- SyntaxError: Non-ASCII character '\xe2' in file 编码错误
Editing .py file in the Notepad: But when run in the PowerShell, I found the follwing error: It seem ...
- C# 静态类与非静态类、静态成员的区别分析
静态类静态类与非静态类的重要区别在于静态类不能实例化,也就是说,不能使用 new 关键字创建静态类类型的变量.在声明一个类时使用static关键字,具有两个方面的意义:首先,它防止程序员写代码来实例化 ...
- 11g导入大量包含子分区的数据时表空间不足
问题描述: ORACLE11g使用impdp数据泵导入时遭遇: ORA-01691: Lob 段 ISCS.SYS_LOB0000100750C00045$$ 无法通过 128 (在表空间 RT_DA ...
- [转]用apache反向代理解决单外网ip对应内网多个web主机的问题
用apache反向代理解决单外网ip对应内网多个web主机的问题 转载一个有独立外网IP,需内网服务器对外发布的例子,是应用apache虚拟主机的. 来源地址:http://www.itshantou ...
- [Math]Sqrt(x)
Total Accepted: 75767 Total Submissions: 314003 Difficulty: Medium Implement int sqrt(int x). Comput ...
- 单光纤udp通信
环境: 两块板子,拥有独立系统(Linux),通过单光纤连接(数据只能单向发送,无反馈).两块板子采用udp协议通信. 问题: 发送板子发送数据后,接收板子上的进程收不到数据. 确认两块光纤 ...