网上这个相关内容有很多,但是大都说的太复杂了,所以这里我想用一篇来对这六个原则做以概括和总结,本文有部分内容摘自网络,由于本人水平有限,错误在所难免,如果我个人的理解有什么不对、不到位的地方,恳请各位高手指出。
1、单一职责原则(SRP:Single Responsibility Principle)
关键句:一个类只负责一个职责
看例子理解:
class Animal{
public void breathe(String animal){
System.out.println(animal+"用肺呼吸");
}
}
public class Client{
public static void main(String[] args){
Animal animal = new Animal();
animal.breathe("牛");
animal.breathe("羊");
animal.breathe("猪");
}
}
运行结果不必多说,但是后面我们发现这个Animal类还会包括鱼,animal.breathe(“鱼”),显然常见的鱼并不是通过肺来呼吸的,这时我们就需要修改了。这里也就发生了职责扩散,职责扩撒就是由于某种原因,职责需要细分为职责1和职责2
我们可能会这样改:
(1)修改Animal类的breathe方法:(不建议的修改方式)
public void breathe(String animal){
if("鱼".equals(animal)){
System.out.println(animal+"用鳃呼吸");
}else{
System.out.println(animal+"用肺呼吸");
}
}
上面这种修改方式是很简单,但是存在进一步的风险,假如往后的某一天,我们知道了某些鱼还会用其他方式呼吸,这样我们又要进一步修改这个类,这时我们的修改可能就会影响到 牛 羊 等呼吸方式。这也违背了单一职责原则,所以这种方式不可取。
(2)增加Animal类新的breathe方法:(根据实际情况确定是否使用)
class Animal{
public void breathe(String animal){
System.out.println(animal+"用肺呼吸");
}
public void breathe2(String animal){
System.out.println(animal+"用鳃呼吸");
}
}
上面修改方式,在方法级别符合单一职责原则,因为它并没有动原来方法的代码。类级别是违背单一职责原则。这种方式在类中方法足够少的情况下可以考虑使用。
(3)细分Animal类:(标准的,不违反单一职责的方式)
class Terrestrial{
public void breathe(String animal){
System.out.println(animal+"用肺呼吸");
}
}
class Aquatic{
public void breathe(String animal){
System.out.println(animal+"用鳃呼吸");
}
}
上面修改的方式,修改花销是很大的,除了将原来的Animal类分解之外,还需要修改客户端。即:
Terrestrial terrestrial = new Terrestrial();
terrestrial.breathe("牛");
Aquatic aquatic = new Aquatic();
aquatic.breathe("鱼");
所以,上面(2)(3)修改方式需要综合项目的复杂程度等选择使用(如何选择使用上面已经有说到)
2、里氏替换原则(LSP:Liskov Substitution Principle)
关键句:子类可以扩展父类的功能,但不能改变父类原有的功能
这个原则主要是针对继承而言的,因为继承往往有这样的含义:父类中已经实现的方法,其实也就是针对该类部分行为的描述和定义。而若子类对这个方法进行任意修改,那么就会造成继承体系的破坏。
实际编程中,我们常常会通过重写父类的方法来完成新的功能,这样写起来虽然简单,但是整个继承体系的代码的可复用性会比较差。
如果非要重写父类的方法,比较通用的做法是:原来的父类和子类都继承一个更通俗的基类,原有的继承关系去掉,采用依赖、聚合,组合等关系代替。
3、依赖倒置原则(DIP:Dependency Inversion Principle)
关键句:细节应该依赖抽象,面向接口编程
看例子理解这个原则:
class Book{
public String getContent(){
return "很久很久以前有一个阿拉伯的故事……";
}
}
class Mother{
public void narrate(Book book){
System.out.println("妈妈开始讲故事");
System.out.println(book.getContent());
}
}
public class Client{
public static void main(String[] args){
Mother mother = new Mother();
mother.narrate(new Book());
}
}
上面的例子有一个Mother类,有一个Book类,Book类作为一个参数传入到Mother类中,这样,Mother类就完成了对Book类的读取。
但是这时候,要增加一个需求,Mother要读报纸,与Book类相似,Newspaper类如下:
class Newspaper{
public String getContent(){
return "林书豪38+7领导尼克斯击败湖人……";
}
}
这时候我们就发现,Mother类的narrate方法只接受Book的对象,并不会读Newspaper,所以我们考虑如下修改方式:
(1)Mother类增加narrate1方法,传入Newspaper。
绝对的坑爹设计,以后如果还有 杂志、小说要读,那是不是会有更多方法需要增加,Mother类需要不断修改。
(2)面向接口编程,引入接口IReader。
interface IReader{
public String getContent();
}
Mother类与接口IReader发生依赖关系,而Book和Newspaper都属于读物的范畴,他们各自都去实现IReader接口,这样就符合依赖倒置原则了,代码修改为:
class Newspaper implements IReader {
public String getContent(){
return "林书豪17+9助尼克斯击败老鹰……";
}
}
class Book implements IReader{
public String getContent(){
return "很久很久以前有一个阿拉伯的故事……";
}
}
class Mother{
public void narrate(IReader reader){
System.out.println("妈妈开始讲故事");
System.out.println(reader.getContent());
}
}
public class Client{
public static void main(String[] args){
Mother mother = new Mother();
mother.narrate(new Book());
mother.narrate(new Newspaper());
}
}
遵循依赖倒置原则可以降低类之间的耦合性,提高系统的稳定性,降低修改程序造成的风险。
4、接口隔离原则(ISP:Interface Segregation Princeple)
关键句:客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上
这个原则解决这样一个问题:
类A通过接口 I 依赖类B,类C通过同样的接口 I 依赖类D。这是接口 I 有类B和类D的方法,但是对于类B和类D他们彼此并不需要对方的方法。这时,接口 I 的就过于臃肿,因此需要拆分。
这个原则比较好理解,这里不再用例子解释。需要记住的就是建立单一接口,不要建立庞大臃肿的接口,适度细化接口,接口中的方法尽量少。
5、迪米特法则(Law of Demeter)/最少知道原则(LKP: Least Knowledge Principle)
关键句:一个对象应该对其他对象保持最少的了解,尽量降低类与类之间的耦合
这个原则也可以这样理解,类仅与直接的朋友通信。(直接的朋友包括:成员变量、方法参数、方法返回值)
举个例子,现在有如下程序:
//总公司员工
class Employee{
private String id;
public void setId(String id){
this.id = id;
}
public String getId(){
return id;
}
}
//分公司员工
class SubEmployee{
private String id;
public void setId(String id){
this.id = id;
}
public String getId(){
return id;
}
}
现在需要输出整个公司员工的Id,我们可能会这样写代码:
class SubCompanyManager{
public List<SubEmployee> getAllEmployee(){
List<SubEmployee> list = new ArrayList<SubEmployee>();
for(int i=0; i<100; i++){
SubEmployee emp = new SubEmployee();
//为分公司人员按顺序分配一个ID
emp.setId("分公司"+i);
list.add(emp);
}
return list;
}
}
class CompanyManager{
public List<Employee> getAllEmployee(){
List<Employee> list = new ArrayList<Employee>();
for(int i=0; i<30; i++){
Employee emp = new Employee();
//为总公司人员按顺序分配一个ID
emp.setId("总公司"+i);
list.add(emp);
}
return list;
}
public void printAllEmployee(SubCompanyManager sub){
List<SubEmployee> list1 = sub.getAllEmployee();
for(SubEmployee e:list1){
System.out.println(e.getId());
}
List<Employee> list2 = this.getAllEmployee();
for(Employee e:list2){
System.out.println(e.getId());
}
}
}
在分公司SubCompanyManager类中创建方法getAllEmployee()
在总公司CompanyManager类中创建方法getAllEmployee()和printAllEmployee()。
根据迪米特法则,ComanyManager与Employee是直接朋友,但是与SubEmployee并不是直接朋友。这样总公司与分公司逻辑上是耦合的了。所以这种方式不可取,需要修改:
class SubCompanyManager{
public List<SubEmployee> getAllEmployee(){
List<SubEmployee> list = new ArrayList<SubEmployee>();
for(int i=0; i<100; i++){
SubEmployee emp = new SubEmployee();
//为分公司人员按顺序分配一个ID
emp.setId("分公司"+i);
list.add(emp);
}
return list;
}
public void printEmployee(){
List<SubEmployee> list = this.getAllEmployee();
for(SubEmployee e:list){
System.out.println(e.getId());
}
}
}
class CompanyManager{
public List<Employee> getAllEmployee(){
List<Employee> list = new ArrayList<Employee>();
for(int i=0; i<30; i++){
Employee emp = new Employee();
//为总公司人员按顺序分配一个ID
emp.setId("总公司"+i);
list.add(emp);
}
return list;
}
public void printAllEmployee(SubCompanyManager sub){
sub.printEmployee();
List<Employee> list2 = this.getAllEmployee();
for(Employee e:list2){
System.out.println(e.getId());
}
}
}
上面方法就有效的降低了类之间耦合,仅与直接的朋友进行了通信。
6、开闭原则(OCP:Open Closed Principle)
关键句:类、模块、功能应该对扩展开放,对修改关闭
开闭原则中“开”,是指对于组件功能的扩展是开放的,是允许对其进行功能扩展的;
开闭原则中“闭”,是指对于原有代码的修改是封闭的,即不应该修改原有的代码。
之前提到的里氏代换原则、依赖倒置原则、接口隔离原则,还有我们知道的抽象类、接口等等,都可以看作是开闭原则的实现方法。