一个厨子可以做出一手好菜,也许他是新东方毕业的或者是祖传秘方。你让他做上一桌佳肴那是简单、快乐而又高效的,然而让他编程就会成为一种苦恼并且让人想不通的一件事。也许这个比喻不是很恰当,但是对于每个类来说,他们就像一个一个的actor,也许是厨子也许是司机,他们应该关注于自己的领域,这样会更加高效而且简明。源于这一思想,我们发现了SRP这个原则,即:单一职责原则。
There should never be more than one reason for a class to change
既然是UML入门,我们也不能跑偏了主题。在讨论SRP原则的同时,我们用实例并用UML类图来表示。
虽然话说的容易,但是实际项目中还是会出现很多问题,例如如何进行责任划分(本来就是一个无法量化的概念)。在探讨一个类是否满足SRP原则时,我们可以思考这个类是否只因一个原因而改变,即其它的原因不能导致该类的修改。在j2ee MVC(Model View Controller) 开发模式中我们知道,Model层只负责业务逻辑,View只负责页面的显示而Cotroller只负责数据的传递。除了页面的改变这一个原因以外,其它任何的变化都不会引起View层代码的改变,这就是SRP原则。在java的面向对象思想中我们也可以找到SRP原则的影子,例如面向对象的三大特性:继承、封装和多态。下面我们就以实例来讨论如何在代码层实现SRP原则。
1.利用接口满足SRP原则
首先我们定义一个Person类,其实就是那个厨子。但是上司让他不仅要做饭还要编程,因此,这个类用UML表示为
当然现在看起来这个类并不是很复杂,因为它并没有太多方法,我们在这只是演示。首先这个类的定义存在缺陷:我们并不是很清晰的知道这个类的职能是什么,它失去了作为厨子的特性。那么,我们可以通过接口来实现
现在我们仔细分析Cooker和Coder这两个接口的作用。在Cooker这个接口类中,我们可以定义所有有关厨艺的方法,不管是鲁菜、川菜还是粤菜都在里面。如果这个厨子比较上进,学习了湘菜,我们也只需要在Cooker中添加相应的方法就OK了,而且不会引起Coder类的任何变化,体现了封装性。
不过这个例子也可以这么来表示:
在这个例子中貌似这两种做法都可行。我们可以这样思考一下:如果这个厨子是新东方毕业的,每个从新东方毕业的学习都是大同小异。这样,我们就可以定义一个Cooker接口,只要是实现这个接口的人,就可以看作是新东方毕业的,都会川菜。如果采用第二种方式的话,那就要定义N个类,然后每个类中都需要写一个川菜的方法,这样显得多余而且没有效率。而且,万一有一天其中的一个厨子学会了开车,我们只需要再实现Drive接口就OK了,其实这也是java中继承和接口的区别,1.我们通过接口实际上定义了一个模板,接口中的方法大多数情况下是已知的,因此可以利用反射的方式,动态的对方法进行修改。2.使用接口,我们可以更好的实现多态,可以把接口当作参数传递。3.我们可以实现一般继承不能实现的多继承。具体有关接口的好处,可以参照http://blog.csdn.net/rocketboy911/article/details/1633414中的实际案例。
2.使用继承来实现SRP原则
在上一部分,我们讨论了继承和接口的区别,在这一部分我们又要来探讨使用继承来实现SRP原则,博主这人还真逗,自相矛盾了。其实不然,这里的继承是指父子类的继承,而非方法的继承。我们来回顾一下刚才的知识,整理一下思路:从新东方毕业的学生都会川菜,那么我们说在这里使用接口而非继承。如果我们把厨子和程序员放在一起,那么他们都是Person,这里我们要用继承而非接口。
在这里我们先忘掉那个倒霉的厨子,我们先定义一个Student类,它拥有read(),dance(),sing()三个方法:
但我们发现,虽然每个同学都会读书,但是并不是每个同学都会跳舞,而且有些还是五音不全。那我们说,这个类还是有问题,我们利用继承对这个类进行修改:
这样我们可以清楚看来:噢!是Lee会唱歌,而David会跳舞,同时他们都继承了Student的read()方法,成为了一个多才多艺的学生。而且以后如果要改变sing()的代码的时候,我们没必要像以前一样,修改整个Student的代码,我们只需要修改Lee的一部分就OK了。
3.有关SRP持久化
这个例子来源于UML for java program这本书。并没有太多的理解,先放在这里。
(上图左边单词拼写错误,摘自书中)
左边说明了Employee和持久化的依存关系,Persistable的改变会或多或少的改变Employee,这是我们不允许的。但是持久化的需求是永远存在的,因此我们可以使用右边的结构,分离开Employee和Persistable的关系,实现SRP原则