单一职责原则_动力节点Java学院整理

定义:不要存在多于一个导致类变更的原因。通俗的说,即一个类只负责一项职责。

问题由来:类T负责两个不同的职责:职责P1,职责P2。当由于职责P1需求发生改变而需要修改类T时,有可能会导致原本运行正常的职责P2功能发生故障。

解决方案:遵循单一职责原则。分别建立两个类T1、T2,使T1完成职责P1功能,T2完成职责P2功能。这样,当修改类T1时,不会使职责P2发生故障风险;同理,当修改T2时,也不会使职责P1发生故障风险。

说到单一职责原则,很多人都会不屑一顾。因为它太简单了。稍有经验的程序员即使从来没有读过设计模式、从来没有听说过单一职责原则,在设计软件时也会自觉的遵守这一重要原则,因为这是常识。在软件编程中,谁也不希望因为修改了一个功能导致其他的功能发生故障。而避免出现这一问题的方法便是遵循单一职责原则。虽然单一职责原则如此简单,并且被认为是常识,但是即便是经验丰富的程序员写出的程序,也会有违背这一原则的代码存在。为什么会出现这种现象呢?因为有职责扩散。所谓职责扩散,就是因为某种原因,职责P被分化为粒度更细的职责P1和P2。

比如:类T只负责一个职责P,这样设计是符合单一职责原则的。后来由于某种原因,也许是需求变更了,也许是程序的设计者境界提高了,需要将职责P细分为粒度更细的职责P1,P2,这时如果要使程序遵循单一职责原则,需要将类T也分解为两个类T1和T2,分别负责P1、P2两个职责。但是在程序已经写好的情况下,这样做简直太费时间了。所以,简单的修改类T,用它来负责两个职责是一个比较不错的选择,虽然这样做有悖于单一职责原则。(这样做的风险在于职责扩散的不确定性,因为我们不会想到这个职责P,在未来可能会扩散为P1,P2,P3,P4……Pn。所以记住,在职责扩散到我们无法控制的程度之前,立刻对代码进行重构。)

举例说明,用一个类描述动物呼吸这个场景:

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类细分为陆生动物类Terrestrial,水生动物Aquatic,代码如下:

class Terrestrial{
  public void breathe(String animal){ 

    System.out.println(animal+"呼吸空气");
 }
}
class Aquatic{
 public void breathe(String animal){
   System.out.println(animal+"呼吸水");
 }
} 

public class Client{
  public static void main(String[] args){
    Terrestrial terrestrial = new Terrestrial();
    terrestrial.breathe("牛");
   terrestrial.breathe("羊");
    terrestrial.breathe("猪");      

    Aquatic aquatic = new Aquatic();
   aquatic.breathe("鱼");
 }
}

运行结果:

牛呼吸空气
羊呼吸空气
猪呼吸空气
鱼呼吸水

我们会发现如果这样修改花销是很大的,除了将原来的类分解之外,还需要修改客户端。而直接修改类Animal来达成目的虽然违背了单一职责原则,但花销却小的多,代码如下:

class Animal{
  public void breathe(String animal){
    if("鱼".equals(animal)){
     System.out.println(animal+"呼吸水");
    }else{
      System.out.println(animal+"呼吸空气");
  } 

  }
} 

public class Client{
 public static void main(String[] args){
    Animal animal = new Animal();
    animal.breathe("牛");
    animal.breathe("羊");
    animal.breathe("猪");
   animal.breathe("鱼");
  }
}

可以看到,这种修改方式要简单的多。但是却存在着隐患:有一天需要将鱼分为呼吸淡水的鱼和呼吸海水的鱼,则又需要修改Animal类的breathe方法,而对原有代码的修改会对调用“猪”“牛”“羊”等相关功能带来风险,也许某一天你会发现程序运行的结果变为“牛呼吸水”了。这种修改方式直接在代码级别上违背了单一职责原则,虽然修改起来最简单,但隐患却是最大的。还有一种修改方式:

class Animal{
  public void breathe(String animal){
    System.out.println(animal+"呼吸空气");
  } 

  public void breathe2(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.breathe2("鱼");
  }
} 

可以看到,这种修改方式没有改动原来的方法,而是在类中新加了一个方法,这样虽然也违背了单一职责原则,但在方法级别上却是符合单一职责原则的,因为它并没有动原来方法的代码。这三种方式各有优缺点,那么在实际编程中,采用哪一中呢?其实这真的比较难说,需要根据实际情况来确定。我的原则是:只有逻辑足够简单,才可以在代码级别上违反单一职责原则;只有类中方法数量足够少,才可以在方法级别上违反单一职责原则;

例如本文所举的这个例子,它太简单了,它只有一个方法,所以,无论是在代码级别上违反单一职责原则,还是在方法级别上违反,都不会造成太大的影响。实际应用中的类都要复杂的多,一旦发生职责扩散而需要修改类时,除非这个类本身非常简单,否则还是遵循单一职责原则的好。
遵循单一职责原的优点有:

  • 可以降低类的复杂度,一个类只负责一项职责,其逻辑肯定要比负责多项职责简单的多;
  • 提高类的可读性,提高系统的可维护性;
  • 变更引起的风险降低,变更是必然的,如果单一职责原则遵守的好,当修改一个功能时,可以显著降低对其他功能的影响。

需要说明的一点是单一职责原则不只是面向对象编程思想所特有的,只要是模块化的程序设计,都适用单一职责原则。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • 简单讲解Java设计模式编程中的单一职责原则

    单一职责原则:一个类,只有一个引起它变化的原因. 为什么需要单一职责原则? 如果一个类有多个原因要去修改它,那么修改一个功能时,可能会让其他功能产生Bug,所以一个类最好只有一个职责.但实际应用中还是比较难实现的,我们只能是尽量符合这个原则. 有时候,开发人员设计接口的时候会有些问题,比如用户的属性和用户的行为被放在一个接口中声明.这就造成了业务对象和业务逻辑被放在了一起,这样就造成了这个接口有两种职责,接口职责不明确,按照SRP的定义就违背了接口的单一职责原则了. 下面是个例子: packag

  • 单一职责原则_动力节点Java学院整理

    定义:不要存在多于一个导致类变更的原因.通俗的说,即一个类只负责一项职责. 问题由来:类T负责两个不同的职责:职责P1,职责P2.当由于职责P1需求发生改变而需要修改类T时,有可能会导致原本运行正常的职责P2功能发生故障. 解决方案:遵循单一职责原则.分别建立两个类T1.T2,使T1完成职责P1功能,T2完成职责P2功能.这样,当修改类T1时,不会使职责P2发生故障风险:同理,当修改T2时,也不会使职责P1发生故障风险. 说到单一职责原则,很多人都会不屑一顾.因为它太简单了.稍有经验的程序员即使

  • 接口隔离原则_动力节点Java学院整理

    定义:客户端不应该依赖它不需要的接口:一个类对另一个类的依赖应该建立在最小的接口上. 问题由来:类A通过接口I依赖类B,类C通过接口I依赖类D,如果接口I对于类A和类B来说不是最小接口,则类B和类D必须去实现他们不需要的方法. 解决方案:将臃肿的接口I拆分为独立的几个接口,类A和类C分别与他们需要的接口建立依赖关系.也就是采用接口隔离原则. 举例来说明接口隔离原则: (图1  未遵循接口隔离原则的设计) 这个图的意思是:类A依赖接口I中的方法1.方法2.方法3,类B是对类A依赖的实现.类C依赖接

  • 高内聚低耦合原则_动力节点Java学院整理

    一.什么是耦合度 软件设计中通常用耦合度和内聚度作为衡量模块独立程度的标准.划分摸块的一个准则就是高内聚低耦合. 耦合度(Coupling)是对模块间关联程度的度量.耦合的强弱取决与模块间接口的复杂性.调用模块的方式以及通过界面传送数据的多少. 模块间的耦合度是指模块之间的依赖关系,包括控制关系.调用关系.数据传递关系.模块间联系越多,其耦合性越强,同时表明其独立性越差.降低模块间的耦合度能减少模块间的影响,防止对某一模块修改所引起的"牵一发动全身"的水波效应,保证系统设计顺利进行.

  • ocp开闭原则_动力节点Java学院整理

    开闭原则(Open Closed Principle)是Java世界里最基础的设计原则,它指导我们如何建立一个稳定的.灵活的系统. 定义: 一个软件实体如类.模块和函数应该对扩展开放,对修改关闭. Softeware entities like classes,modules and functions should be open for extension but closed for modifications. 开闭原则的含义是说一个软件实体应该通过扩展来实现变化,而不是通过修改已有代码

  • Java依赖倒转原则_动力节点Java学院整理

    定义:高层模块不应该依赖低层模块,二者都应该依赖其抽象:抽象不应该依赖细节:细节应该依赖抽象. 问题由来:类A直接依赖类B,假如要将类A改为依赖类C,则必须通过修改类A的代码来达成.这种场景下,类A一般是高层模块,负责复杂的业务逻辑:类B和类C是低层模块,负责基本的原子操作:假如修改类A,会给程序带来不必要的风险. 解决方案:将类A修改为依赖接口I,类B和类C各自实现接口I,类A通过接口I间接与类B或者类C发生联系,则会大大降低修改类A的几率. 依赖倒置原则基于这样一个事实:相对于细节的多变性,

  • 里氏代换原则_动力节点Java学院整理

    聊一聊里氏代换原则. 定义1:如果对每一个类型为 T1的对象 o1,都有类型为 T2 的对象o2,使得以 T1定义的所有程序 P 在所有的对象 o1 都代换成 o2 时,程序 P 的行为没有发生变化,那么类型 T2 是类型 T1 的子类型. 定义2:所有引用基类的地方必须能透明地使用其子类的对象. 问题由来:有一功能P1,由类A完成.现需要将功能P1进行扩展,扩展后的功能为P,其中P由原有功能P1与新功能P2组成.新功能P由类A的子类B来完成,则子类B在完成新功能P2的同时,有可能会导致原有功能

  • 合成聚合复用原则_动力节点Java学院整理

    合成聚合复用原则 合成复用原则又称为组合/聚合复用原则(Composition/Aggregate Reuse Principle, CARP),其定义如下: 合成复用原则(Composite Reuse Principle, CRP):尽量使用对象组合,而不是继承来达到复用的目的. 合成复用原则就是在一个新的对象里通过关联关系(包括组合关系和聚合关系)来使用一些已有的对象,使之成为新对象的一部分:新对象通过委派调用已有对象的方法达到复用功能的目的.简言之:复用时要尽量使用组合/聚合关系(关联关

  • 开放封闭原则_动力节点Java学院整理

    定义:软件实体(类.模块.函数等)应该是可以扩展的,但是不可修改.对于扩展是开放的,对于更改是封闭的.关键是抽象,将一个功能的通用部分和实现细节部分清晰的分离开来. 这里要求我们写代码要有抽象的概念.什么是抽象?指由实体抽离出概念的思考过程.就是从众多的对象中抽离出共同的本质的特征.在写代码的过程中,需要抽象类的地方,只需要抓住这个类的本质功能,不要老想着它在这个工程中的具体功能. 我们继续看开放封闭原则,这个原则要求,将一个功能的共用部分和实现部分清晰地分离开来.因为在最初搭建架构的时候你不能

  • web压力测试工具_动力节点Java 学院整理

    0. Grinder –  Grinder是一个开源的JVM负载测试框架,它通过很多负载注射器来为分布式测试提供了便利. 支持用于执行测试脚本的Jython脚本引擎HTTP测试可通过HTTP代理进行管理.根据项目网站的说法,Grinder的 主要目标用户是"理解他们所测代码的人--Grinder不仅仅是带有一组相关响应时间的'黑盒'测试.由于测试过程可以进行编码--而不是简单地脚本 化,所以程序员能测试应用中内部的各个层次,而不仅仅是通过用户界面测试响应时间. 1. Pylot -Pylot 是

  • Java枚举_动力节点Java学院整理

    enum 的全称为 enumeration, 是 JDK 5  中引入的新特性,存放在 java.lang 包中. 原始的接口定义常量 public interface IConstants { String MON = "Mon"; String TUE = "Tue"; String WED = "Wed"; String THU = "Thu"; String FRI = "Fri"; String

随机推荐