六大设计原则其三:依赖倒置原则
依赖倒置原则的定义
涉及到三个概念:
模块(高层模块和底层模块--原子模块)
抽象:接口或者抽象类
细节:实现类
三者之间的关系:
模块依赖于抽象;
细节依赖于抽象,抽象不依赖与细节;
即所谓的面向接口编程(OOD)
优点:减少类之间的耦合性,提高系统稳定性,降低并行开发引起的风险,提高代码的可读性和可维护性。
变更之间见真功夫。好的程序就是周围环境在频繁变化时,我自岿然不动。
并行开发的风险:风险扩散。不使用依赖倒置原则就会单线程,一个人作为另一个人接着做。要使用并发,就要使用依赖倒置原则来降低类之间的耦合关系,从而减少并发风险。
变更时最好只修改高层模块,使业务可以正常运行,把“变更”带来的风险降到最低。
JAVA变量的两种类型:表面类型和实际类型
并行开发实际举例:TDD(Test-Driven Development)—-测试驱动开发
定义:先写好单元测试类,再写实现类。
重点:抽象是对实现的约束,对依赖者而言,不仅仅约束自己,同时也约束自己与外部的关系。
依赖的三种写法(对比spring框架的IOC DI依赖注入功能实现):
构造函数传递依赖对象。
Setter方法传递依赖对象
接口声明传入对象(在声明接口时将依赖的对象传入)
最佳实践
理解依赖正置和倒置的区别。
正置:类之间的依赖是实实在在的依赖,面相实现编程
倒置:对事物之间的关系进行抽象,根据系统设计要求产生抽象之间的依赖。
依赖倒置原则的核心:
面向接口编程!!!!
项目中依赖倒置原则的具体实践:
- 每个类尽量都要有接口或者抽象类。(基本要求)
- 变量的表面类型尽量是接口或者是抽象类。
- 任何类都不应该从具体类中派生(有的时候没有必要一定要去继承最高的基类,通过一个继承关系,覆写几个方法就完事了)
- 尽量不要对基类的抽象方法进行覆写,防止破坏抽象之间的依赖关系。
- 可以结合里氏替换原则进行使用。
六大设计原则之<接口隔离原则>:
JAVA中接口分为两种:
1.实例接口:从某一种角度来看,JAVA中的类也是一种借口。 Person person = new Person();
2.类接口:interface
接口隔离原则的简单理解:
类之间的依赖关系应该尽量建立在最小接口上。(建立单一接口,不要建立臃肿的接口;接口尽量细化,接口中的方法尽量细化。)
从这个角度来说,要注意和单一职责原则的区别。
单一职责原则:注重的是职责,这是业务逻辑上的划分。
接口隔离原则:注重的是接口功能实现的细化,方法尽量少。
接口的设计太过于庞大,包含的可变因素就多,对于代码后期的可维护性和可扩展性是不利的,封装过度。
通过分散定义多个接口,可以预防未来变更的扩散,提高系统的灵活性和可维护性。
重点:设计是有限度的,不能无限的考虑未来的变更情况,这样才不至于陷入设计的泥潭中不能自拔,终极目的还是业务功能的实现。
保证接口的纯洁性
实现接口隔离原则:
- 接口要尽量小。(根据接口隔离原则对接口进行拆分时,首先必须要满足单一职责原则)----换句话来说,就是不要拆的太狠。
- 接口要高内聚,提高接口、类、模块的处理能力,减少与外界的交互。即在接口中尽量少用public方法,减少对外的承诺,承诺越少对系统的开发就越为有利,变更的风险也就越小,同时有利于降低开发成本。
- 定制服务
举例说明:IBookSearch接口,拆分为
ISimpleBookSearcher IComplexBookSearcher
不同的接口根据权限的不同开放给不同的客户使用。如果写在一起,未免权限划分不开,大家都能干某几件事(比较占用系统资源),这时候就会影响线上程序的整体性能。
4.接口的设计是有限度的
还是那句话,接口功能粒度划分的不是越细越好,会带来结构复杂化,开发难度增加,可维护性降低
最佳实践
1.接口或者类尽量使用原子接口或者原子类来组装。
2.一个接口尽量只服务于一个子模块或者业务逻辑。
3.压缩接口中的public方法
六大设计原则之<迪米特法则> LoD Law of Demeter
也称为最少知识原则(least knowledge principle),简单定义就是一个类对自己需要耦合或者调用的类(组合、聚合、依赖等)知道的最少。
我只关注与你暴露出来供我使用的public方法,其他内部实现细则不论多复杂我一概不管。
四层含义:
1.只和“朋友进行交流”
先定义一下朋友类:
出现在成员变量、方法的输入输出参数中的类称为朋友类,出现在方法体内部的类不是朋友类。
迪米特法则要求我们的类只和自己的朋友类进行交流。(类与类之间的关系是建立在类之间的,而不是方法间,因此尽量不要再方法中引入一个类中不存在的对象。)
方法是类的一个行为,类不允许自己的行为与其他类产生依赖关系是不自知的。
这样可以提高系统的健壮性,降低系统之间的耦合性。
- “朋友类之间的关系不能太过于紧密”
如果两个类的关系太过于紧密(例如:一个类向另一个类暴露了太多的方法),就会造成耦合关系过于牢固。
例如,A类向B类暴露了很多的方法,A类修改其中的一个方法时,调用A类方法的B类就要做出相应修改,这样就增加了修改变更的风险。
重点:一个类公开的public属性或者方法越多,修改时涉及到的面就越广,变更引起的风险扩散也就越大。
在设计时,可以考虑是否可以修改为private、package-private、protected等访问权限,是否可以加上final关键字等。
3.方法的放置问题:
如果一个方法放置在本类中也可以,放在其他类中也不错。那么这时候对方法的归属问题遵循一个原则:
如果一个方法放置在本类中,既不增加类间关系,也不会对本类产生负面影响,就可以放置在本类中。
4.谨慎使用Serializable
最佳实践:
迪米特法则的核心观念就是类之间的解耦、弱耦合。只有弱耦合之后,类的复用率才会提升。这种要求的结果就是会产生大量的中转或者跳转类,导致系统的复杂性提高,同时也为维护带来了难度。规则的应用,既要确保结构清晰,又要最大化的实现类之间的高内聚低耦合。-------解耦是有限度的。
设计模式六大原则之“开闭原则”
开闭原则是JAVA中最基础的设计原则。
开闭原则的定义:一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。
具体解释来说,就是一个软件实体应该通过扩展来实现变化,而不是通过修改已有的代码来实现变化。
软件实体包括以下几个部分:
项目或者软件产品中按照一定逻辑规则划分的模块。
抽象或者类
方法
软件产品在其生命周期内,都会发生变化,开闭原则正是已在设计时尽量适应这些变化,提高项目的稳定性和灵活性,真正实现“拥抱变化”为目的。
对于已经投产的项目来说,应对需求变化有几种方式:
- 修改接口。
在接口上添加方法,所有的实现类都实现该方法。但是这样的方式是不推崇的。一方面,实体类依据接口的变化做出的改变也很多,这是极其不方便的;另一方面,接口本身作为一种契约,应该是稳定和可靠的,经常发生变化就会失去这样的效能。
2.修改实体类
在项目中经常采用的方法,这种方法在项目有明确的章程或者优良的架构设计时,是一种非常优秀的方法。
但是,有的时候也会因为直接修改实体类而出现信息不对称的情况(例如:直接在书籍实体类中加入了打折程序,在调用getprice后,采购人员希望获得的是原价,这个时候因为返回了打折后的价格就会出现信息不对称造成决策失误。)
3.通过扩展来实现变化
可以添加新的模块来实现特定的功能。
注意:开闭原则对扩展开放、对修改关闭。这并不意味着对原代码不做任何修改。底层模块的变更,必然会引起高层模块进行耦合,否则就是一个孤立无异议的代码片段。
常见的变化可以分为三种类型:
- 逻辑变化
只变化一个逻辑,而不影响其他模块。
2.子模块变化
一个模块的变化会对其他模块产生影响。
特别是低层次的模块变化必然会影起高层次模块的变化。
3.可见视图的变化
重点:保持代码历史的纯洁性,放弃修改历史的想法。
一个项目的开发路径:
项目开发、重构、测试、投产、运维
其中的重构可以对原有的设计和代码进行修改,运维尽量减少对原有代码的修改,保持历史代码的纯洁性,提高系统稳定性。