1.单一职责

一个类只负责一个功能领域中的相应职责,或者可以定义为:就一个类而言,应该只有一个引起它变化的原因。

单一职责原则是实现高内聚、低耦合的指导方针,它是最简单但又最难运用的原则。

如果一个类承担的职责过多,就等于把这些职责耦合在一起了。一个职责的变化可能会削弱或者抑制这个类完成其他职责的能力。就比如界面和数据,要拆开。

解决方案:

将不同的职责封装到不同的类或模块中。一个类只负责一项职责。

 

2.开闭原则(Open Close Principle)

一个软件实体应当对扩展开放,对修改关闭。即软件实体应尽量在不修改原有代码的情况下进行扩展。

开闭原则是一个非常虚的原则,其余5个原则是对开闭原则的具体解释。

就像“好好学习,天天向上”的口号一样,告诉我们要好好学习,但是学什么,怎么学并没有告诉我们,需要去体会和掌握,开闭原则也是一个口号,那我们怎么把这个口号应用到实际工作中呢?

解决方案:

  • 使用接口和抽象类,为系统定义一个相对稳定的抽象层,而将不同的实现行为移至具体的实现层中完成;
  • 参数类型、引用对象尽量使用接口或者抽象类,而不是实现类;
  • 抽象层尽量保持稳定,一旦确定即不允许修改;

为了满足开闭原则,需要对系统进行抽象化设计,抽象化是开闭原则的关键。在Java、C#等编程语言中,可以为系统定义一个相对稳定的抽象层,而将不同的实现行为移至具体的实现层中完成。想要达到这样的效果,我们需要使用接口和抽象类,可以通过它们定义系统的抽象层,再通过具体类来进行扩展。如果需要修改系统的行为,无须对抽象层进行任何改动,只需要增加新的具体类来实现新的业务功能即可,实现在不修改已有代码的基础上扩展系统的功能,达到开闭原则的要求。

 

3.里氏代换原则(Liskov Substitution Principle)

任何基类可以出现的地方,子类一定可以出现。

LSP 是继承复用的基石,只有当派生类可以替换掉基类,且软件单位的功能不受到影响时,基类才能真正被复用,而派生类也能够在基类的基础上增加新的行为。里氏代换原则是对开闭原则的补充。实现开闭原则的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。

遵循里氏替换原则时,子类尽量不去重写基类的方法,如果需要大量重写时,建议再创造一个基类的基类,并继承之,使得其和之前的基类是兄弟关系,而不是父子关系;

 

4.依赖倒转原则(Dependence Inversion Principle)

抽象(接口/抽象类)不应该依赖于细节,细节应该依赖于抽象(接口/抽象类)。
高层模块不应该依赖于低层模块,两者都应该依赖其抽象。

这个原则是开闭原则的基础,具体内容:

抽象指的是接口或者抽象类。细节指的是具体实现类。

中心思想是面向接口编程;针对接口编程,依赖于抽象而不依赖于具体。

比如有接口ICar,子类有Car1和Car2.我们在处理逻辑的时候,直接调用ICar的方法,而不是在代码里调用Car1或者Car2.

如何在项目中使用这个规则呢?

  • 每个类尽量都有接口或者抽象类,或者都有
  • 变量的表面类型尽量是接口或者抽象类

 

5.接口隔离原则(Interface Segregation Principle)

客户端不应该依赖它不需要的接口
类间的依赖关系应该建立在最小的接口上

其实通俗来理解就是,客户端在调用方法时,应当依赖于某个需要的接口,而不是一个很大很广泛的接口或者类。这就要求我们不要在一个接口里面放很多的方法,这样会显得这个类很臃肿不堪。所以接口应该尽量拆分,一个接口对应一种功能。

或许看到接口隔离原则这样的定义很多人会觉得和单一职责原则很像,但是这两个原则还是有着很鲜明的区别。接口隔离原则和单一职责原则的审视角度是不同的,单一职责原则要求类和接口职责单一,注重的是职责,是业务逻辑上的划分,而接口隔离原则要求方法要尽可能的少,是在接口设计上的考虑。例如一个接口的职责包含10个方法,这10个方法都放在一个接口中,并且提供给多个模块访问,各个模块按照规定的权限来访问,并规定了“不使用的方法不能访问”,这样的设计是不符合接口隔离原则的,接口隔离原则要求“尽量使用多个专门的接口”,这里专门的接口就是指提供给每个模块的都应该是单一接口(即每一个模块对应一个接口),而不是建立一个庞大臃肿的接口来容纳所有的客户端访问。

在项目中,我们会经常这样做:

类A:
{
    类B b;
}

类B:
{
    Fun1();
    Fun2();
}

一个类A,即使只需要类B的某些功能时,还是会持有一个类B的实例。这样相当于把整个类B的所有功能都暴露给了外部,这样是不好的,我们可以修改为类A只持有需要功能的接口实例。因此需要将类B的所有功能拆分为N个接口,继承接口并实现方法。

 

接口隔离原则是对接口进行规范约束,其包含以下4层含义:

  • 接口要尽量小:这是核心定义,不出现臃肿的接口,要尽量拆分为n个小接口。但是首先要满足单一职责。
  • 接口要高内聚:提高接口,类,模块的处理能力,较少对外的交互。
  • 定制服务:单独为一个个体提供优良的服务,只提供访问者需要的方法。
  • 接口的设计是有限度的:接口的设计粒度越小,系统越灵活。但是灵活的同时,也带来了结构的复杂化。那么这个度如何判断呢?没有标准,看开发者的经验和常识了。

总结就是:使用多个隔离的接口,比使用单个功能广泛的接口要好。

 

6.迪米特法则,又称最少知道原则(Demeter Principle)

一个对象应该对其他对象有最少的了解

一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。

迪米特法则对类的低耦合提出的明确的要求,包含了以下含义:

  • 只和朋友交流:朋友类的定义是这样的:出现在成员变量、方法的输入输出参数中的类称为成员朋友类,而出现在方法体内部的类不属于朋友类。
  • 朋友也是有距离的:迪米特法则要求类“羞涩”一点,尽量不要对外公布太多的public方法和非静态的public变量,尽量内敛,多使用private、package-private、protected等访问权限。
  • 是你自己的就是你自己的:在实际应用中经常会出现这样一个方法:放在本类中也可以,放在其他类中也没有错,那怎么去衡量呢?正确做法:如果一个方法放在本类中,既不增加类间关系,也对本类不产生负面影响,那就放置在本类中。

迪米特法则的核心观念就是类间解耦,弱耦合,只有弱耦合了以后,类的复用率才可以提高。

1.单一职责

一个类只负责一个功能领域中的相应职责,或者可以定义为:就一个类而言,应该只有一个引起它变化的原因。

单一职责原则是实现高内聚、低耦合的指导方针,它是最简单但又最难运用的原则。

如果一个类承担的职责过多,就等于把这些职责耦合在一起了。一个职责的变化可能会削弱或者抑制这个类完成其他职责的能力。就比如界面和数据,要拆开。

解决方案:

将不同的职责封装到不同的类或模块中。一个类只负责一项职责。

 

2.开闭原则(Open Close Principle)

一个软件实体应当对扩展开放,对修改关闭。即软件实体应尽量在不修改原有代码的情况下进行扩展。

开闭原则是一个非常虚的原则,其余5个原则是对开闭原则的具体解释。

就像“好好学习,天天向上”的口号一样,告诉我们要好好学习,但是学什么,怎么学并没有告诉我们,需要去体会和掌握,开闭原则也是一个口号,那我们怎么把这个口号应用到实际工作中呢?

解决方案:

  • 使用接口和抽象类,为系统定义一个相对稳定的抽象层,而将不同的实现行为移至具体的实现层中完成;
  • 参数类型、引用对象尽量使用接口或者抽象类,而不是实现类;
  • 抽象层尽量保持稳定,一旦确定即不允许修改;

为了满足开闭原则,需要对系统进行抽象化设计,抽象化是开闭原则的关键。在Java、C#等编程语言中,可以为系统定义一个相对稳定的抽象层,而将不同的实现行为移至具体的实现层中完成。想要达到这样的效果,我们需要使用接口和抽象类,可以通过它们定义系统的抽象层,再通过具体类来进行扩展。如果需要修改系统的行为,无须对抽象层进行任何改动,只需要增加新的具体类来实现新的业务功能即可,实现在不修改已有代码的基础上扩展系统的功能,达到开闭原则的要求。

 

3.里氏代换原则(Liskov Substitution Principle)

任何基类可以出现的地方,子类一定可以出现。

LSP 是继承复用的基石,只有当派生类可以替换掉基类,且软件单位的功能不受到影响时,基类才能真正被复用,而派生类也能够在基类的基础上增加新的行为。里氏代换原则是对开闭原则的补充。实现开闭原则的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。

遵循里氏替换原则时,子类尽量不去重写基类的方法,如果需要大量重写时,建议再创造一个基类的基类,并继承之,使得其和之前的基类是兄弟关系,而不是父子关系;

 

4.依赖倒转原则(Dependence Inversion Principle)

抽象(接口/抽象类)不应该依赖于细节,细节应该依赖于抽象(接口/抽象类)。
高层模块不应该依赖于低层模块,两者都应该依赖其抽象。

这个原则是开闭原则的基础,具体内容:

抽象指的是接口或者抽象类。细节指的是具体实现类。

中心思想是面向接口编程;针对接口编程,依赖于抽象而不依赖于具体。

比如有接口ICar,子类有Car1和Car2.我们在处理逻辑的时候,直接调用ICar的方法,而不是在代码里调用Car1或者Car2.

如何在项目中使用这个规则呢?

  • 每个类尽量都有接口或者抽象类,或者都有
  • 变量的表面类型尽量是接口或者抽象类

 

5.接口隔离原则(Interface Segregation Principle)

客户端不应该依赖它不需要的接口
类间的依赖关系应该建立在最小的接口上

其实通俗来理解就是,客户端在调用方法时,应当依赖于某个需要的接口,而不是一个很大很广泛的接口或者类。这就要求我们不要在一个接口里面放很多的方法,这样会显得这个类很臃肿不堪。所以接口应该尽量拆分,一个接口对应一种功能。

或许看到接口隔离原则这样的定义很多人会觉得和单一职责原则很像,但是这两个原则还是有着很鲜明的区别。接口隔离原则和单一职责原则的审视角度是不同的,单一职责原则要求类和接口职责单一,注重的是职责,是业务逻辑上的划分,而接口隔离原则要求方法要尽可能的少,是在接口设计上的考虑。例如一个接口的职责包含10个方法,这10个方法都放在一个接口中,并且提供给多个模块访问,各个模块按照规定的权限来访问,并规定了“不使用的方法不能访问”,这样的设计是不符合接口隔离原则的,接口隔离原则要求“尽量使用多个专门的接口”,这里专门的接口就是指提供给每个模块的都应该是单一接口(即每一个模块对应一个接口),而不是建立一个庞大臃肿的接口来容纳所有的客户端访问。

在项目中,我们会经常这样做:

类A:
{
    类B b;
}

类B:
{
    Fun1();
    Fun2();
}

一个类A,即使只需要类B的某些功能时,还是会持有一个类B的实例。这样相当于把整个类B的所有功能都暴露给了外部,这样是不好的,我们可以修改为类A只持有需要功能的接口实例。因此需要将类B的所有功能拆分为N个接口,继承接口并实现方法。

 

接口隔离原则是对接口进行规范约束,其包含以下4层含义:

  • 接口要尽量小:这是核心定义,不出现臃肿的接口,要尽量拆分为n个小接口。但是首先要满足单一职责。
  • 接口要高内聚:提高接口,类,模块的处理能力,较少对外的交互。
  • 定制服务:单独为一个个体提供优良的服务,只提供访问者需要的方法。
  • 接口的设计是有限度的:接口的设计粒度越小,系统越灵活。但是灵活的同时,也带来了结构的复杂化。那么这个度如何判断呢?没有标准,看开发者的经验和常识了。

总结就是:使用多个隔离的接口,比使用单个功能广泛的接口要好。

 

6.迪米特法则,又称最少知道原则(Demeter Principle)

一个对象应该对其他对象有最少的了解

一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。

迪米特法则对类的低耦合提出的明确的要求,包含了以下含义:

  • 只和朋友交流:朋友类的定义是这样的:出现在成员变量、方法的输入输出参数中的类称为成员朋友类,而出现在方法体内部的类不属于朋友类。
  • 朋友也是有距离的:迪米特法则要求类“羞涩”一点,尽量不要对外公布太多的public方法和非静态的public变量,尽量内敛,多使用private、package-private、protected等访问权限。
  • 是你自己的就是你自己的:在实际应用中经常会出现这样一个方法:放在本类中也可以,放在其他类中也没有错,那怎么去衡量呢?正确做法:如果一个方法放在本类中,既不增加类间关系,也对本类不产生负面影响,那就放置在本类中。

迪米特法则的核心观念就是类间解耦,弱耦合,只有弱耦合了以后,类的复用率才可以提高。