单一职责原则(Single Responsibility Principle)
定义:一个对象应该只包含单一的职责,并且该职责被完整地封装在一个类中。即:不要存在多于一个导致类变更的原因。通俗的说,就是一个类只负责一项职责。
问题由来:类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,用它来负责两个职责是一个比较不错的选择,虽然这违背了单一职责原则。
举例说明,用一个类描述动物吃东西这个场景:
class Animal{ public void eat(String animal){ System.out.println(animal+"吃草"); } } public class Client{ public static void main(String[] args){ Animal animal = new Animal(); animal.eat("牛"); animal.eat("羊"); animal.eat("马"); } } 运行结果: 牛吃草 羊吃草 马吃草
程序上线后,发现问题了,并不是所有的动物都是吃草的,比如狼就是吃肉的。修改时如果遵循单一职责原则,需要将Animal类细分为食草动物Herbivore和肉食性动物Carnivora,代码如下:
class Herbivore{ public void eat(String animal){ System.out.println(animal+"吃草"); } } class Carnivora{ public void eat(String animal){ System.out.println(animal+"吃肉"); } } public class Client{ public static void main(String[] args){ Herbivore herbivore = new Herbivore(); herbivore.eat("牛"); herbivore.eat("羊"); herbivore.eat("马"); Carnivora carnivora = new Carnivora(); carnivora.eat("狼"); } } 运行结果: 牛吃草 羊吃草 马吃草 狼吃肉
我们发现这样修改花销是很大的,除了将原来的类分解之外,还需要修改客户端。而直接修改类Animal来达成目的虽然违背了单一职责原则,但花销却要小的多,代码如下:
class Animal{ public void eat(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.eat("牛"); animal.eat("羊"); animal.eat("马"); animal.eat("狼"); } }
可以看到,这种修改方式要简单得多,但是却存在着隐患:有一天需要将狼分为北极狼和非北极狼,则又需要修改Animal类的eat方法,而对原有代码的修改会调用“牛”、“羊”、“马”等相关功能带来风险,也许某一天你会发现程序运行结果变成了“非北极狼吃草”了。这种修改方式直接在代码级别上违背了单一职责原则,虽然修改起来最简单,但是隐患也是最大的。还有一种修改方式:
class Animal{ public void eat(String animal){ System.out.println(animal+"吃草"); } public void eat2(String animal){ System.out.println(animal+"吃肉"); } } public class Client{ public static void main(String[] args){ Animal animal = new Animal(); animal.eat("牛"); animal.eat("羊"); animal.eat("马"); animal.eat2("狼"); } }
可以看到,这种修改方式没有改动原来的方法,而是在类中新加了一个方法,这样虽然也违背了单一职责原则,但在方法级别上却是符合单一职责原则的,因为它并没有动原来方法的代码。
这三种方式各有优缺点,那么在实际编程中,采用哪一种呢?其实这真的比较难说,需要根据实际情况来确定。我的原则是:只有逻辑足够简单,才可以在代码级别上违反单一职责原则;只有类中方法数量足够少,才可以在方法级别上违反单一职责原则。在实际应用中的类都要复杂得多,一旦发生职责扩散而需要修改类时,除非这个类本身非常简单,否则还是遵循单一职责原则的好。
遵循单一职责原则的优点:
1、降低类的复杂性,类的职责清晰明确;
2、提高类的可读性和可维护性;
3、变更引起的风险降低,变更是必然的,如果单一职责原则遵守的好,当修改一个功能时,可以显著降低对其他功能的影响。
原文:http://www.cnblogs.com/lhws/archive/2012/03/10/2389189.html
原文链接:https://www.f2er.com/javaschema/283620.html