1 单一职责原则
1.1、基本介绍
所谓单一职责原则是对类的要求,即一个类应该只负责一项职责,如类A负责职责1和职责2,当职责1需求变更时来改变类A,可能会引起职责2的执行错误,所以应该将类A 分解为类A1和A2。
1.2、单一职责原则注意事项
- 出于降低类的复杂度和可维护扩展性,一个类只负责一项职责。
- 为了降低类变更引起的风险
- 通常情况下,我们应当遵循类的单一职责原则,只有业务逻辑足够简单,类才可以违反单一职责原则:当类中的方法足够少时,可以在方法级别保持单一职责原则。
2 接口隔离原则
2.1、基本介绍
客户端类不应该依赖它不需要的接口,即一个类通过接口对另一个类的依赖应该建立在最小的接口上。
如图所示,有一个接口interface1里面共有五个方法 class C 和 class D 对interface1做出了不同的实现。
用于服务class A 和class B,class A 依赖通过呢class c实现interface1的 method1,method2,method3,而class B
依赖class D 实现interface1的method1,method4,method5方法。C 和 D都需要实现Interface1的所有方法,但是在使用的情况中明显有冗余(假设暂时没有其他的使用场景),这就违反了接口隔离的原则。这就需要做接口的分割。应该遵循如下图所示的关系。
3 依赖倒转原则(dependency inversion principle)
3.1 基本介绍
依赖倒转(倒置)原则是指:
(1)高层模块不应该依赖底层模块,二者都应该依赖其抽象(接口或抽象父类),比如在springmv框架中 controller层应该依赖service接口,而不是去依赖dao层。
(2)抽象不应该依赖细节,细节应该依赖于抽象
(3)依赖倒转的中心思想是面向接口编程。
(4)依赖倒转原则基于这样的设计理念:相对细节的多变性,抽象的东西要稳定的多,以抽象为基础搭建的架 构要比以细节为基础搭建的架构稳定的多。在java中,抽象是指接口或抽象类,细节是指具体的实现类。
(5)是用接口或抽象类的目的是先制定好规范,不涉及具体的操作,把战线细节的任务交给具体的实现类去完 成。这样就好像先搭起程序的骨架,再去添砖加瓦。使开发工程化。
3.2 依赖关系传递的三种方式及案例
3.2.1 接口传递
/**
* @Description:
* @author: snow
* @Date: 2020-03-02
**/
public class PrincipleInversion {
public static void main(String[] args) {
useApplePcCoding coder = new useApplePcCoding();
coder.turnOnPc(new ApplePc());
}
}
interface PC{
String run();
}
interface Coding{
void turnOnPc(PC pc);
}
class ApplePc implements PC{
@Override
public String run() {
return "turn on my apple pc";
}
}
class useApplePcCoding implements Coding{
@Override
public void turnOnPc(PC pc) {
System.out.println(pc.run() + " and start coding ");
}
}
3.2.2 构造方法传递
/**
* @Description:
* @author: snow
* @Date: 2020-03-02
**/
public class PrincipleInversion2 {
public static void main(String[] args) {
UseLenovoPcCoding coder = new UseLenovoPcCoding(new LevovoPc());
coder.turnOnPc();
}
}
interface PC{
String run();
}
interface Coding{
void turnOnPc();
}
class LevovoPc implements PC{
@Override
public String run() {
return "turn my lenovo pc ";
}
}
class UseLenovoPcCoding implements Coding{
private PC pc;
@Override
public void turnOnPc() {
System.out.println(pc.run() + "and start coding");
}
public UseLenovoPcCoding(PC pc) {
this.pc = pc;
}
}
3.2.3 setter方法传递
跟构造器方法传递类似,不再赘述。
3.3 注意事项
低层模块尽量要有抽象类或接口或者两者都有,程序稳定性更好。
变量的声明尽量是接口或者抽象类这样我们的对象和引用之间就存在一个缓冲层,利于程序的扩展。
4 里氏替换原则
4.1 OO 中的继承性问题
1) 继承包含了这样一层含义:父类中但凡已近实现好的方法,实际上是在设定规范和契约,虽然它不强制要求子类必须遵守契约,但是子类中对父类方法的改写可能会对整个继承体系造成破坏。
2)继承在给程序设计带来便利的同时也带来了一定的弊端,。比如使用继承会给程序带来侵入性,程序的可移植性降低,增加对象间的耦合性,如果一个类被多个其他类继承,当修改这个类时可能会引起子类中出现故障。
问题提出:在编程时怎么正确的使用继承 ? ===>里氏替换原则
4.2 里氏替换原则
1) 里氏替换原则由麻省理工学院一位姓里的女士提出
2)如果对每个类型的为T1的对象O1,都有类型为T2的对象O2,使得以T1类型定义的所有程序p的对象O1都被替换成O2时,程序p的行为没有发生变换,那么T2的类型是T1的子类。换句话说,所有引用基类的地方都必须能够透明的使用其子类的对象
3)在使用继承时,遵循里氏替换原则,子类尽量不要重写父类的放法。
4)使用继承使得类的耦合性变高了,可以使用聚合,组合,依赖来降低类的耦合性。
5 开闭原则
5.1 基本介绍
1)开闭原则(open closed principle ocp) 是编程中最基础、最重要的设计原则。
2)一个软件的实体类,模块和函数应该对扩展开放(对于提供方),对修改关闭(对使用方),用抽象构建框架,实现扩展功能。
3)编程中遵循其他原则,以使用设计模式的目的就是遵循开闭原则。
5.2用一例子来理解它
有这样一个简单的需求:有一个类负责画不同的形状,这是不同形状的实体类就是提供方,提供给上面的类来处理,上面的类就是使用方,怎么实现这个功能?也许会想到if/else判断不同的形状,使用方对不同的形状采取不同的动作,但是当有新的形状需求,则使用方的代码也需要改变,这就违反了ocp原则。
遵循ocp原则应该按照这种思想来实现:
/**
* @Description:
* @author: snow
* @Date: 2020-03-02
**/
public class OcpDemo {
public static void main(String[] args) {
Editor editor = new Editor();
editor.drawShape(new MyCircle());
editor.drawShape(new Rectangle());
}
}
//使用方,在真是开发环境中可能对应于一个module或者package,这里只是简单的举例,写在一起
class Editor{
private Object field;
public void drawShape(Shape shape){
shape.draw();
}
}
//提供方
//当有新的形状需求时,只需要在提供方扩展新的实体类即可
abstract class Shape{
private Object filed;
public abstract void draw();
}
class Rectangle extends Shape{
@Override
public void draw() {
System.out.println("draw rectangle");
}
}
class MyCircle extends Shape{
@Override
public void draw() {
System.out.println("draw circle");
}
}
6 迪米特法则
1)一个对象应该对其他对象保持最少的了解
2)类与类关系越密切,耦合度越高
3)迪米特法则又叫最少知道法则,即一个类对自己所依赖的类知道的越少越好,也就是说,对于被依赖的类,尽 量将逻辑代码封装在内的内部,对外只提供Public方法,不对外泄露任何信息。
4)只与直接的朋友通信。
5)直接朋友:只要两个对象之间有耦合关系,我们就说这两个对象之间具有朋友关系,其中我们称出现在**成员变量, ** 方法参数类型 ,方法返回值类型中的类为直接朋友
7 合成复用原则
尽量使用合成聚合的方式,少使用继承
针对接口编程而不是针对实现编程。