源码分析七大设计原则之合成复用原则(Composite Reuse Principle)
2021/12/19 9:19:50
本文主要是介绍源码分析七大设计原则之合成复用原则(Composite Reuse Principle),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
源码分析之七大设计原则
一、合成复用原则(Composite Reuse Principle)
定义:就是说要尽量的使用合成和聚合,而不是继承关系达到复用的目的
该原则就是在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分:新的对象通过向这些对象的委派达到复用已有功能的目的
二、合成复用原则的重要性
通常类的复用分为继承复用和合成复用两种,继承复用虽然简单易实现,但它也存在诸多缺点
1、继承复用破坏了类的封装性。因为继承会将基类的实现细节暴露给派生类,基类对派生类是透明的,所以这种复用又称为 “白箱” 复用
2、派生类与基类的耦合度高。基类的实现的任何改变都会导致派生类的实现发生变化,这不利于类的扩展与维护
3、它限制了复用的灵活性。从基类继承而来的实现是静态的,在编译时已经定义,所以运行时不可能发生变化
4、当复用派生类的时候,如果继承下来的实现不适合解决新的问题,则基类必须重写或者被其它更适合的类所替换,这种依赖关系限制了灵活性,最终限制了复用性
采用合成复用时,可以将已有对象纳入新对象中,使之成为新对象的一部分,新对象可以调用已有对象的功能,它有诸多优点
1、维持了类的封装性。因为成分对象的内部细节是新对象看不见的,所以这种复用又称为 “黑箱” 复用
2、新旧类之间的耦合度低。这种复用所需的依赖较少,新对象存取成分对象的唯一方法是通过成分对象的接口
3、复用的灵活性高。这种复用可以在运行时动态进行,新对象可以动态地将新的责任委派到合适的对象
4、每一个新的类可以将焦点集中在一个任务上
当然合成复用有一定缺陷(再好的东西也会有一定的缺陷),通过这种方式复用建造的系统会有较多的对象需要管理
三、源码演练
需求: 制作一个集合,要求该集合能记录曾经添加过多少个元素
反例代码 v1 版本
class MySet extends HashSet { private int count = 0; public boolean add(Object obj) { count++; return super.add(obj); } @Override public boolean addAll(Collection c) { count += c.size(); return super.addAll(c); } public int getCount() { return count; } }
public class Client { public static void main(String[] args) { Set set2 = new HashSet(); set2.add("Java性能优化权威指南"); MySet set = new MySet(); set.addAll(set2); System.out.println(set.getCount()); } }
最终输出 2,因为基类的 addAll 回调了 add 方法
反例代码 v2 版本,由于 addAll 会回调 add 方法,导致累加不正确,修改代码:MySet 类不重写 addAll,client 类不变
class MySet extends HashSet { private int count = 0; public boolean add(Object obj) { count++; return super.add(obj); } public int getCount() { return count; } }
看似好像没有问题了,但是目前的代码必须依赖于 HashSet 的 addAll 方法必须回调 add 方法,如果 JDK 版本升级,addAll 不在回调 add 方法,那么自定义的 MySet 将会出问题,依赖性太强
反例代码 v3 版本,针对 v2 版本的问题,修改如下:MySet 重写 addAll,不再做 count + c.size() 操作,而是保证 addAll 一定回调 add 方法
class MySet extends HashSet { private int count = 0; public boolean add(Object obj) { count++; return super.add(obj); } @Override public boolean addAll(Collection c) { boolean bln = false; for(Object obj : c) { if (add(c)) { bln = true; } } return bln; } public int getCount() { return count; } }
最终输出 4,看似好像没有问题了,但其实又有问题,始终围绕着 HashSet 在转,依赖性太强
1、万一 JDK 更新(好比 HashMap 底层数据结构每个版本不太一样),HashSet 多了入口方法 addOne(),MySet 没有重写,会导致程序错误
2、目前重写了 add、addAll 两个方法,万一在 HashSet 中有方法依赖于这两个方法,会导致业务错误
最终完美的 v4 版本,MySet 不在继承 HashSet,让 MySet 与 HashSet 发生关联关系
class MySet { private Set set = new HashSet(); private int count = 0; public boolean add(Object obj) { count++; return set.add(obj); } public boolean addAll(Collection c) { count += c.size(); return set.addAll(c); } public int getCount() { return count; } }
JDK 的反例教材,为了复用 remove、get、put 方法,Stack extends Vector,从而导致了栈不是栈
public class Client { public static void main(String[] args) { Stack<String> stack = new Stack<>(); // 入栈出栈 FILO 先进后出 // 入栈 stack.push("A"); stack.push("B"); // 出栈 System.out.println(stack.pop()); // 输出 B System.out.println(stack.pop()); // 输出 A System.out.println(stack.remove(0)); // 输出 A,这就不是先进后出 System.out.println(stack.get(0)); // 输出 A,这就不是先进后出 } }
四、温馨提示
反例教材不代表以后不在使用继承、方法重写。是否使用继承取决于基类与派生类的作者是否为同一人:因为如果不是同一人,那么基类作者不知道也不会管派生类重写了什么方法,而派生类也预知不了基类未来会增加什么方法
如果只是为了复用代码,应当使用组合关系,组合大于继承。使用继承关系,难免会出现问题。如果要使用继承关系,则必须严格遵循里氏替换原则。合成复用原则和里氏替换原则相辅相成的,两者都是开闭原则的具体实现规范
这篇关于源码分析七大设计原则之合成复用原则(Composite Reuse Principle)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-23增量更新怎么做?-icode9专业技术文章分享
- 2024-11-23压缩包加密方案有哪些?-icode9专业技术文章分享
- 2024-11-23用shell怎么写一个开机时自动同步远程仓库的代码?-icode9专业技术文章分享
- 2024-11-23webman可以同步自己的仓库吗?-icode9专业技术文章分享
- 2024-11-23在 Webman 中怎么判断是否有某命令进程正在运行?-icode9专业技术文章分享
- 2024-11-23如何重置new Swiper?-icode9专业技术文章分享
- 2024-11-23oss直传有什么好处?-icode9专业技术文章分享
- 2024-11-23如何将oss直传封装成一个组件在其他页面调用时都可以使用?-icode9专业技术文章分享
- 2024-11-23怎么使用laravel 11在代码里获取路由列表?-icode9专业技术文章分享
- 2024-11-22怎么实现ansible playbook 备份代码中命名包含时间戳功能?-icode9专业技术文章分享