面向对象设计原则的组成和重用原则。
转载自:https://blog.csdn.net/lovelion/article/details/7563441
组合原则,也称为组合/聚合重用原则(carp),定义如下:
合成复用原则(Composite Reuse Principle, CRP):尽量使用对象组合,而不是继承来达到复用的目的。
组合的原理是通过关联关系(包括组合关系和聚合关系)将一个新对象中已有的一些对象利用起来,使它们成为新对象的一部分。新对象通过委托现有对象的方法来达到重用功能的目的。简而言之,应该尽可能使用组合/聚合关系(关联关系),少用继承。
在面向对象设计中,有两种方法可以在不同的环境中重用现有的设计和实现,即通过组合/聚合关系或通过继承,但首先要考虑组合/聚合。组合/聚合可以使系统更加灵活,减少类与类之间的耦合,一个类的变化对其他类的影响相对较小。其次,要考虑继承。使用继承时,需要严格遵循Richter替换原则。有效利用继承有助于理解问题,降低复杂性,而滥用继承会增加系统构建和维护的难度,增加系统的复杂性。因此,谨慎使用继承重用是必要的。
通过继承进行重用的主要问题是继承重用会破坏系统的封装,因为继承会将基类的实现细节暴露给子类。由于基类的内部细节通常对子类可见,这种重用也被称为“白盒”重用。如果基类改变了,子类的实现也必须改变。从基类继承的实现是静态的,不能在运行时更改,并且不够灵活。此外,继承只能在有限的环境中使用(例如,没有声明一个类不能被继承)。
发展
对于继承的深刻理解,可以参考《软件架构设计》的作者虞雯写的文章—— 《见山只是山见水只是水——提升对继承的认识》。
因为组合或聚合关系可以将现有对象(也称为成员对象)合并到新对象中,并使其成为新对象的一部分,所以新对象可以调用现有对象的函数,这可以使成员对象的内部实现细节对新对象不可见。所以,这种复用也叫“黑箱”复用。与继承关系相比,其耦合度相对较低,成员对象的变化对新对象影响不大。合成可以在运行时动态完成,新对象可以动态引用与成员对象相同类型的其他对象。
一般来说,如果两个类之间的关系是“Has-A”,就应该用组合或者聚合,如果是“Is-A”,就可以用继承。“Is-A”是一个严格的分类学定义,意思是一个类是另一个类的“一种”;和“有-有”不同,它意味着某个角色有一定的责任。
下面是一个简单的例子来加深对复合复用原理的理解:
在Sunny Software公司早期的CRM系统设计中,考虑到客户数量少,系统使用MySQL作为数据库,需要将CustomerDAO类等与数据库操作相关的类连接到数据库,将连接数据库的方法getConnection()封装在DBUtil类中。由于需要重用DBUtil类的getConnection()方法,设计人员将CustomerDAO作为DBUtil类的子类,初步设计方案结构如图1所示。
图1初步设计方案结构图。
随着客户数量的增加,系统决定升级到Oracle数据库,因此需要添加一个新的OracleDBUtil类来连接Oracle数据库。由于在最初的设计方案中,CustomerDAO和DBUtil之间存在继承关系,所以在改变数据库连接方式时,需要修改CustomerDAO类的源代码,将CustomerDAO做成OracleDBUtil的子类,这样会违反开闭原则。【当然也可以修改DBUtil类的源代码,这样也会违反开闭原则。】
现在利用复合复用的原理进行重构。
按照复合复用的原则,在实现复用时要多关联少继承。因此,在这个例子中,我们可以使用关联复用代替继承复用,重构后的结构如图2所示:
图2改造后的结构图。
在图2中,CustomerDAO和DBUtil之间的关系从继承变为关联,DBUtil对象通过依赖注入注入到CustomerDAO中,或者通过构造注入,或者通过Setter注入。如果需要扩展DBUtil的功能,可以通过它的子类来实现,比如通过子类OracleDBUtil连接Oracle数据库。由于CustomerDAO是为DBUtil编程的,根据Richter替换原理,DBUtil子类的对象可以覆盖DBUtil对象,子类扩展的方法只能通过将子类注入CustomerDAO来使用。比如通过将OracleDBUtil对象注入CustomerDAO,可以实现Oracle数据库连接,不需要修改原代码,可以灵活添加新的数据库连接方式。
内容来源网络,如有侵权,联系删除,本文地址:https://www.230890.com/zhan/70037.html