1:单一职责原则
单一职责原则:一个类只负责一个功能领域中的相应职责,或者可以定义为:就一个类而言,应该只有一个引起它变化的原因
换句话说就是一个接口只做一件事,即一个职责一个接口。但是困难的是划分职责时并没有一个标准,最终都是需要从实际的项目去考虑。我们在设计的时候,尽量单一,然后对于其实现类就要多方面的考虑。不能死套单一职责原则,否则会增加很多类,给维护带来不便。
单一职责原则是最简单的面向对象设计原则,它用于控制类的粒度大小
单一指责原则告诉我们:一个类不能太“累”,在软件系统中,一个类(大到模块,小到方法)承担的职责越多,它被复用的可能性就越小,而且一个类承担的职责过多,就相当于将这些职责藕合在一起,当其中一个职责变化时,可能会影响到其他职责的运作,因此要将这些职责进行分离,将不同的职责封装不同的类中,即将不同的变化原因封装在不用的类中,如果多个职责总是同时发生改变则可将它们封装在同一类中。
单一职责原则是实现高内聚,低耦合的指导方针,它是最简单也同时是最难运用的原则,需要开发人员发现类的不同职责从而将其分离。
2:里氏替换原则
在学习java类的继承时,我们知道继承有一些优点:
1:子类拥有父类的所有方法和属性,从而可以减少创建类的工作量。
2:提高了代码的重用性。
3:提高了代码的扩展性,子类不但拥有了父类的所有功能,还可以添加自己的功能。
但又有点也同样存在缺点:
1:继承是侵入性的。只要继承,就必须拥有父类的所有属性和方法。
2:降低了代码的灵活性。因为继承时,父类会对子类有一种约束。
3:增强了耦合性。当需要对父类的代码进行修改时,必须考虑到对子类产生的影响。有时修改了一点点代码都有可能需要对打断程序进行重构。
如何扬长避短呢?方法是引入里氏替换原则。
定义:
第一种定义:
如果对每一个类型为S的对象o1,都有类型为T的对象o2,使得以T定义的所有程序P在所有的对象o1都代换成o2时,程序P的行为没有发生变化,那么类型S是类型T的子类型。
第二种定义:
所有引用基类的地方必须能透明地使用其子类的对象。
通俗的说就是只要有父类出现的地方,都可以用子类来替代,而且不会出现任何错误和异常。但是反过来则不行,有子类出现的地方,不能用其父类替代。
里氏替换原则对继承进行了规则上的约束,这种约束主要体现在四个方面:
1:子类必须实现父类的抽象方法,但不得重写(覆盖)父类的非抽象(已实现)方法。
2:子类中可以增加自己特有的方法。
3:当子类覆盖或实现父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松(继承链上下层)。
4:当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。(子类重写或者实现父类的方法时,方法的返回值可以和父类一致,也可以比父类小(继承链下层))
3:依赖倒置原则
定义:
是程序要依赖于抽象接口,不要依赖于具体实现。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户(调用者)与实现(实现者)模块间的耦合。(面向接口编程 所有的依赖都是依赖接口或者依赖抽象 维护,)
面向过程的开发,上层调用下层,上层依赖于下层,当下层剧烈变化时,上层也要跟着变化,这就会导致模块的复用性降低而且大大提高了开发的成本。 面向对象的开发很好的解决了这个问题,一般的情况下抽象的变化概率很小,让用户程序依赖于抽象,实现的细节也依赖于抽象。即使实现细节不断变化,只要抽象不变,客户程序就不需要变化。这大大降低了客户程序域实现细节的耦合度。
抽象不应该依赖细节,细节应该依赖于抽象。说白了,就是针对接口编程,不要针对实现编程。
依赖倒置原则包含三层含义:
1:高层模块不应该依赖低层模块,两者都应该依赖其抽象;
2:抽象不应该依赖细节;
3:细节应该依赖抽象。
依赖倒置有三种方式来实现(DI 依赖注入)
1、通过构造函数传递依赖对象; 比如在构造函数中的需要传递的参数是抽象类或接口的方式实现。
2、通过setter方法传递依赖对象; 即在我们设置的setXXX方法中的参数为抽象类或接口,来实现传递依赖对象。
3、接口声明实现依赖对象,也叫接口注入;
即在函数声明中参数为抽象类或接口,来实现传递依赖对象,从而达到直接使用依赖对象的目的。
原则:面向接口编程。抽象指的是接口或抽象类,细节就是具体的实现类,使用接口或者抽象类的目的是制定好规范和契约,而不去涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成。
注意:
1. 代码中传递参数或关联关系时,尽量引用高层的抽象层类,即使用接口和抽象类进行变量声明、参数类型声明、方法返回类型声明以及数据类型的转换;
2. 当一个对象和其他对象有依赖关系时,可以利用依赖注入的方法将类之间进行解耦,主要有:构造注入、Set方法注入和接口注入;
3. 开闭是原则,里氏是基础,依赖倒置是手段;
4. 低层模块尽量都要有抽象类或接口,或者两者都有;
5. 变量的声明类型尽量是抽象类或接口;
6. 使用继承时遵循里氏替换原则。
好处:
1. 可降低类之间的耦合性;
2. 可提高系统的稳定性;
3. 可降低修改程序所造成的风险。
在大多数情况下,这三个设计原则会同时出现,开闭原则是目标,里氏代换原则是基础,依赖倒转原则是手段,它们相辅相成,相互补充,目标一致,只是分析问题时所站角度不同而已。
4:接口隔离原则
1.定义: 建立单一接口,不要建立臃肿庞大的接口。即接口尽量细化,同时接口中的方法尽量少。在这里提一下单一职责和接口隔离原则的区别。首先两个侧重点是不一样的,
单一职责要求类和接口,或者方法的职责单一,侧重点在职责,这是根据业务逻辑进行划分的。而接口隔离原则要接口中的方法尽量少。比如,一个接口或者一个中有十个方法,不同的方法做不同的事情,但是这个接口总体就是处理一件事情,然后具体细分成了10个方法。不同的模块根据不同的权限进行访问,这是符单一职责原则的。但是按照接口隔离的原则是要求接口接口中的方法尽量少,落实到这个实例就是要求尽量多几个专门的接口供不同的模块使用,而不是只有一个臃肿的接口,依据权限去限制不同模块可以访问的方法。
2.接口隔离原则是对接口定义的规范。含义主要包含以下4点。
①接口尽量小。根据具体业务把一个接口按照接口隔离原则细化成更多的接口。但是在此基础之上必须不能违背单一职责原则。
②接口要高内聚。高内聚的意思就是提高接口和类处理能力,减少对外的交互。接口是对外的承诺,因此设计时应该尽量少公布接口中的public方法,承诺越少系统开发越有利且变更风险就越少。
③定制服务。定制服务就是单独为一个个体提供服务,即只提供访问者需要的方法。
举一个图书管理系统的例子,有一个查询接口BookSearch,包括如下方法:searchById,searchByBookName,searchByCategory,complexSearch,其中前三个方法是提供给学生使用的,后一个方法是提供给管理员使用的,学生对这个方法的访问是有限制的,调用不会返回任何值。当这四个方法全部公布出去之后,学生对此方法的访问即使不返回任何值也会使服务器性能下降。因此合理的设计应该是拆分这个接口为两个接口:SimpleSearch和AdminSearch。SimpleSearch接口提供searchById,searchByBookName,searchByCategory方法,AdminSearch接口提供complexSearch方法,此时学生实现SimpleSearch接口即可,管理员同时实现SimpleSearch和AdminSearch两个接口。
④接口设计是有限度的。接口设计越小越好,但是结构同时会变得复杂,维护也变得难了。因此就要把握住这个度。
5: 迪米特法则
不要和陌生人说话,一个对象应当对其他对象有尽可能少的了解。一个软件实体应当尽可能少的与其他实体发生相互作用。每一个软件单位对其他的单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位。
迪米特法则的初衷在于降低类之间的耦合。由于每个类尽量减少对其他类的依赖,因此,很容易使得系统的功能模块功能独立,相互之间不存在(或很少有)依赖关系。
迪米特法则还有一个更简单的定义:只与直接的朋友通信
首先来解释一下什么是直接的朋友:每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间是朋友关系。
耦合的方式很多,依赖、关联、组合、聚合等。其中,我们称出现成员变量、方法参数、方法返回值中的类为直接的朋友,而出现在局部变量中的类则不是直接的朋友。
也就是说,陌生的类最好不要作为局部变量的形式出现在类的内部。
迪米特法则不希望类之间建立直接的联系。如果真的有需要建立联系,也希望能通过它的友元类来转达。因此,应用迪米特法则有可能造成的一个后果就是:系统中存在大量的中介类,这些类之所以存在完全是为了传递类之间的相互调用关系——这在一定程度上增加了系统的复杂度。
迪米特法则还强调在类的结构设计上,每一个类都应当降低成员的访问权限,也就是说,一个类包装好自己的private状态,不需要让别的类知道的字段或行为就不要公开。
迪米特法则强调了下面两点:
1:从被依赖者的角度:只暴露应该暴露的方法或属性,即编写相关的类时确定方法和属性的权限
2:从依赖者的角度来看,只依赖应该依赖的对象
例如:用户关闭计算机()(saveCurrentTask() 保存当前的任务->closeService()(关闭计算机服务)->closeScreen()(关闭屏幕)->closePower()(关闭电源))
计算机类(用户只有一个操作,关机 就不要把上述四个方法都定义为public 用户挂你的需要调用四个步骤 closePower()à调用其它三个方法 可以把其他三个方法定义为private)
6:开闭原则
定义:一个软件实体如类、模块和函数应该对扩展开放,对改动关闭。
问题由来:在软件的生命周期内,由于变化、升级和维护等原因须要对软件原有代码进行改动时,可能会给旧代码中引入错误。也可能会使我们不得不正确整个功能进行重构,而且须要原有代码经过又一次測试。
解决方式:当软件须要变化时。尽量通过扩展软件实体的行为来实现变化。而不是通过改动已有的代码来实现变化。
开闭原则是面向对象设计中最基础的设计原则。它指导我们怎样建立稳定灵活的系统。开闭原则可能是设计模式六项原则中定义最模糊的一个了,它仅仅告诉我们对扩展开放,对改动关闭,但是究竟怎样才干做到对扩展开放。对改动关闭,并没有明白的告诉我们。
曾经,假设有人告诉我“你进行设计的时候一定要遵守开闭原则”。我会觉的他什么都没说,但貌似又什么都说了。由于开闭原则真的太虚了。
事实上。我们遵循设计模式前面5大原则,以及使用23种设计模式的目的就是遵循开闭原则。也就是说,仅仅要我们对前面5项原则遵守的好了,设计出的软件自然是符合开闭原则的。这个开闭原则更像是前面五项原则遵守程度的“平均得分”,前面5项原则遵守的好,平均分自然就高,说明软件设计开闭原则遵守的好。假设前面5项原则遵守的不好。则说明开闭原则遵守的不好。
开闭原则无非就是想表达这样一层意思:用抽象构建框架。用实现扩展细节。由于抽象灵活性好。适应性广,仅仅要抽象的合理。能够基本保持软件架构的稳定。而软件中易变的细节。我们用从抽象派生的实现类来进行扩展,当软件须要发生变化时,我们仅仅须要依据需求又一次派生一个实现类来扩展就能够了。当然前提是我们的抽象要合理,要对需求的变更有前瞻性和预见性才行。
讲到这里,再回忆一下前面说的5项原则。恰恰是告诉我们用抽象构建框架。用实现扩展细节的注意事项而已:单一职责原则告诉我们实现类要职责单一;里氏替换原则告诉我们不要破坏继承体系。依赖倒置原则告诉我们要面向接口编程。接口隔离原则告诉我们在设计接口的时候要精简单一。迪米特法则告诉我们要减少耦合。
而开闭原则是总纲。他告诉我们要对扩展开放,对改动关闭。
最后说明一下怎样去遵守这六个原则。
对这六个原则的遵守并非是和否的问题,而是多和少的问题,也就是说。我们一般不会说有没有遵守,而是说遵守程度的多少。不论什么事都是过犹不及,设计模式的六个设计原则也是一样,制定这六个原则的目的并非要我们刻板的遵守他们,而须要依据实际情况灵活运用。对他们的遵守程度仅仅要在一个合理的范围内,就算是良好的设计。