public class RestfulClient {
Public RestfulClient(IHttp http) {
this.http = http != null ? http : createHttp();
public void doGet(String url, List<Header> list, Body body) {
...
http.sendRequest(request);
public static IHttp createHttp() {
int runtimeSDKApi = Build.VERSION.SDK_INT;
if (runtimeSDKApi >= GINGERBREAD_SDK_NUM) {
return new HttpUrlConnImpl();
return new HttpClientImpl();
上述代码中, RestfulClient 类中依赖的是 IHttp 接口,而通过 createHttp 函数返回的是 IHttp 的实现类 HttpClientImpl 或 HttpUrlConnImpl。这就是所谓的里氏替换原则,任何父类、父接口出现的地方子类都可以出现,这不就保证了可扩展性吗!如果我们现在将网络请求库改成 OKHttp, 是不是将依赖传进去就行了。
代码共享,减少创建类的工作量,每个子类都拥有父类的方法和属性。
提高代码的重用性。
提高代码的可扩展性,实现父类的方法就可以“为所欲为”了,很多开源框架的扩展接口都是通过继承父类来完成的。
提高产品或项目的开放性。
继承是侵入性的。只要继承,就必须拥有父类所有的属性和方法。
降低了代码的灵活性。子类必须父类的属性和方法,让子类自由的世界中多了些约束。
增强了耦合性。当父类的常亮、变量和方法被修改时,必须要考虑子类的修改,而且在缺乏规范的环境下,这种修改可能带来非常糟糕的后果---大量的代码需要重构。
依赖倒置原则(Dependence Inversion Principle)
电脑在以前维修的话是根本不可能的事,可是现在却特别容易,比如说内存坏了,买个内存条,硬盘坏了,买个硬盘换上。为啥这么方便?从修电脑里面就有面相对象的几大设计原则,比如单一职责原则,内存坏了,不应该成为更换CPU的理由,它们各自的职责是明确的。再比如开放-封闭原则,内存不够只要插槽足够就可以添加。还有依赖倒转原则,原话解释是抽象不应该依赖细节,细节应该依赖于抽象,说白了,就是要针对接口编程,不要对实现编程,无论主板,CPU,内存,硬盘都是针对接口设计的,如果是针对实现来设计,内存就要对应的某个品牌的主板,那就会出现换内存需要把主板也换了的尴尬。
为什么叫反转呢?
面对过程开发时,为了使得常用代码可以复用,一般都会把这些常用代码写成许许多多函数的程序库,这样我们做新项目时,去调用这些底层的函数就可以了。比如我们做的项目大多要访问数据库,所以我们就把访问数据库的代码写成了函数,每次做新项目时就去调用,这就叫做高层模块依赖底层模块。
但是要做新项目是 业务逻辑的高层模块都是一样的,客户却希望使用不同的数据库或存储信息方式,这时出现麻烦了。我们希望能再次利用这些高层模块,但高层模块都是与底层的访问数据库绑定在一起的,没办法复用这些高层模块,这就非常糟糕了。就像刚才说的,PC里如果CPU,内存,硬盘都是需要依赖具体的主板,主板一坏,所有的部件都没法用了,显然不合理,而如果不管高层模块还是底层模块,它们都依赖于抽象,具体一点就是接口或者抽象类,只要接口是稳定的,那么任何一个的更改都不用担心其它受影响,这就使得无论高层模块还是底层模块都可以很容易被复用,这才是最好的办法。
在前面我们的例子中, RefusClient 实现类依赖于 IHttp 接口(抽象),而不依赖于 HttpClientImpl 与 HttpUrlConnImpl 实现类(细节),这就是依赖倒置原则的体现。
传递依赖关系有三种方式,以上的例子中使用的方法是接口传递,另外还有两种传递方式:构造方法传递和 setter 方法传递,相信用过 Dagger、Spring 框架的,对依赖的传递方式一定不会陌生。 在实际编程中,我们一般需要做到如下:
低层模块尽量都要有抽象类或接口,或者两者都有。
变量的声明类型尽量是抽象类或接口。
使用继承时遵循里氏替换原则。 依赖倒置原则的核心就是要我们面向接口编程。
可扩展性好
接口隔离原则(Interface Segregation Principle)
接口隔离原则(英语:interface-segregation principles, 缩写:ISP)指明没有客户(client)应该被迫依赖于它不使用方法。接口隔离原则(ISP)拆分非常庞大臃肿的接口成为更小的和更具体的接口,这样客户将会只需要知道他们感兴趣的方法。这种缩小的接口也被称为角色接口(role interfaces)。接口隔离原则(ISP)的目的是系统解开耦合,从而容易重构,更改和重新部署。接口隔离原则是在SOLID (面向对象设计)中五个面向对象设计(OOD)的原则之一,类似于在GRASP (面向对象设计)中的高内聚性。
下面是看到网上的举的例子:
可能描述起来不是很好理解,我们还是以示例来加强理解吧。 我们知道,在网络框架中,网络队列中是会对请求进行排序的。内部使用 PriorityBlockingQueue 来维护网络请求队列,PriorityBlockingQueue 需要调用 Request 类的排序方法就可以了,其他的接口他根本不需要,即 PriorityBlockingQueue 只需要 compareTo 这个接口,而这个 compareTo 方法就是我们所说的最小接口方法,而是 Java 中的 Comparable 接口,但我们这里是指为了学习,至于哪里定义的无关紧要。
在元素排序时,PriorityBlockingQueue 只需要知道元素是个 Comparable 对象即可,不需要知道这个对象是不是 Request 类以及这个类的其他接口。它只需要排序,因此,只要知道它是实现了 Comparable 对象即可,Comparable 就是它的最小接口,也是通过 Comparable 隔离了 PriorityBlockingQueue 类对 Request 类的其他方法的可见性。
很多人会觉的接口隔离原则跟之前的单一职责原则很相似,其实不然。其一,单一职责原则原注重的是职责;而接口隔离原则注重对接口依赖的隔离。其二,单一职责原则主要是约束类,其次才是接口和方法,它针对的是程序中的实现和细节;而接口隔离原则主要约束接口接口,主要针对抽象,针对程序整体框架的构。 采用接口隔离原则对接口进行约束时,要注意以下几点:
接口尽量小,但是要有限度。对接口进行细化可以提高程序设计灵活性是不挣的事实,但是如果过小,则会造成接口数量过多,使设计复杂化。所以一定要适度。
为依赖接口的类定制服务,只暴露给调用的类它需要的方法,它不需要的方法则隐藏起来。只有专注地为一个模块提供定制服务,才能建立最小的依赖关系。
提高内聚,减少对外交互。使接口用最少的方法去完成最多的事情。 运用接口隔离原则,一定要适度,接口设计的过大或过小都不好。设计接口的时候,只有多花些时间去思考和筹划,才能准确地实践这一原则。
迪米特原则( Law of Demeter)
迪米特原则(Law of Demeter,缩写LoD)等同于“最少知识原则(Principle of Least Knowledge)”,是一种软件开发的设计指导原则,特别是面向对象的程序设计。迪米特原则是松耦合的一种具体案例。该原则是美国东北大学在1987年末在发明的,可以简单地以下面任一中方式总结。
每个单元对于其他的单元只能拥有有限的知识:只是与当前单元紧密联系的单元; 每个单元只能和它的朋友交谈:不能和陌生单元交谈; 只和自己直接的朋友交谈。 这个原理的名称来源于希腊神话中的农业女神,孤独的得墨忒耳。
很多面向对象程序设计语言用"."表示对象的域的解析算符,因此迪米特原则可以简单地陈述为“只使用一个.算符”。因此,a.b.Method()违反了此定律,而a.Method()不违反此定律。一个简单例子是,人可以命令一条狗行走(walk),但是不应该直接指挥狗的腿行走,应该由狗去指挥控制它的腿如何行走。
通俗地讲,一个类应该对自己需要耦合或者调用的类知道得最少,这有点类似于接口隔离原则中的最小接口的概念。类的内部如何实现、如何复杂都与调用者或者依赖者没有关系,调用者或者依赖者只需要知道它需要它需要的方法即可,其他的一概不关心。类与类之间的关系越密切,耦合度越大,当一个类发生改变时,对另一个类的影响也越大。
降低复杂度
降低耦合性
增加稳定性
开闭原则(Open-Close Principle)
开闭原则是 Java 世界里最基础的设计原则,它指导我们如何建立一个稳定的、灵活的系统。开闭原则的定义是:一个软件实体类,模块和函数应该对扩展开放,对修改关闭。在软件的生命周期内,因为变化、升级和维护等原因,需要对软件原有的代码进行修改时,可能会给旧代码引入错误。因此,当软件需要变化时,我们应该尽量通过扩展的方式来实现变化,而不是通过修改已有的代码来实现。
在软件开发过程中,永远不变的就是变化。开闭原则是使我们的软件系统拥抱变化的核心原则之一。对扩展开放,对修改关闭这样的高层次概括,即在需要对软件进行升级、变化时应该通过扩展的形式来实现,而非修改原有代码。当然这只是一种比较理想的状态,是通过扩展还是通过修改旧代码需要依据代码自身来定。
还是上面的例子, HttpUrlConnImpl 和 HttpClientImpl 实现了 IHttp 接口, 当我们实现 OKHttp 的时候就可以实现 HttpOKImpl, 这样通过扩展的形式来应对软件的变化或者说用户需求的多样性,既避免了破坏原有系统,又保证了软件系统的可维护性。依赖于抽象,而不依赖于具体,使得对扩展开放,对修改关闭。开闭原则与依赖倒置原则,里氏替换原则一样,实际上都遵循一句话:面向接口编程。
增加稳定性
可扩展性高
避免掉进过度设计的怪圈
当你掌握一些设计模式或者手法之后,比较容易出现的问题就是过度设计。有的人甚至在一个应用中一定要将 23 种常见的设计模式运用上,这就本末倒置了。设计模式的四大要素中就明确指出,模式的运用应该根据软件系统所面临的问题来决定是否需要使用现有的设计。也就是说,再出现问题或者你预计会出现那样的问题时,才推荐使用特定的设计模式,而不是将各种设计模式套进你的软件中。
不管在设计、实现、测试之间有多少时间都应该避免过度设计,它会打破你的反馈回路,使你的设计得不到反馈,从而慢慢陷入危险中。所以你只需要保持简单的设计,这样就有时间来测试该设计是否真的可行,然后作出最后的决策。
当设计一款软件时,从整体高度上设定一种架构模式,确定应用的整体架构,然后再分析一些重要的设计思路,并且保证他们的简单性、清晰性,如果有时间可以使用 Java 代码模拟一个简单的原型,确保设计是可行的,最后就可以付诸行动了。切实不要过度的追求设计,适当就好,当我们发现或者预计到将要出现问题时,在判断是否需要运用设计模式。