定义
There should never be more than one reason for a class to change.
应该有且仅有一个原因引起类的变更。
优点
降低类的复杂性
每个类实现单一职责,并且单一职责都有清楚明确的定义,复杂性当然降低。
提高可读性
类的复杂性降低了,当然提高了可读性了。
提高可维护性
类的复杂性降低,可读性好,当然好维护。
易于测试
测试单一目标的类只需要很少的测试类。让“用测试替代文档” “self documentation by tests”变得更加容易
易于调试
在一个单一职责类找到问题是一件更容易的事情。
变更引起的风险降低,变更是必不可少的,如果接口的单一职责做的好,一个接口修改只对相应的实现类有影响,对其它的接口没有影响,这对系统的扩展性,维护性都是有好处的。
类的单一职责原则
一般一个对象可以分为属性和行为二部分,所以在类的设计时,我们一般把对象的属性抽象成一个BO(Business Object,业务对象),把对象的行为抽象成一个Biz(Business Logic,业务逻辑)。
产生原因
没有任何的程序设计人员不清楚应该写出高内聚低耦合的程序,但是很多耦合常常发生在不经意之间,其原因就是:职责扩散:因为某种原因,某一职责被分化为颗粒度更细的多个职责了
解决办法
遵守单一职责原则,将不同的职责封装到不同的类或模块中。
场景模拟
我们需要创作一个论坛,需要发布主题和回帖功能
场景模拟UML 图
简单代码
@protocol Thread<NSObject>
-(void)addReplayMessage;
@end
#import "Thread.h"
@protocol Forum<NSObject>
-(void)addThread:(id<Thread>)thread;
-(void)addReplayMessage;
@end
#import "Forum.h"
@interface ForumObject : NSObject<Forum>
@end
#import "ForumObject.h"
@interface ForumObject()
@property (nonatomic,strong) id<Thread>thread;
@end@implementation ForumObject
-(void)addThread:(id<Thread>)thread{
self.thread = thread;
}
-(void)addReplayMessage{
[self.thread addReplayMessage];
}
@end
#import "Thread.h"
@interface ThreadObject : NSObject<Thread>
@property (nonatomic,strong) NSString *name;
@end
#import "ThreadObject.h"
@implementation ThreadObject
-(void)addReplayMessage{
NSLog(@"%@ 回复信息",self.name);
}
@end
测试代码
id<Forum> forumObject = [ForumObject new];
ThreadObject * threadobject = [ThreadObject new];
threadobject.name =@"单一职责原则";
[forumObject addThread:threadobject];
[forumObject addReplayMessage];
测试结果
2018-04-04 14:39:55.425610+0800 设计模式原则[90446:6274362] 单一职责原则 回复信息
分析
我们都知道一个论坛的结构一般是
forum------->Thread----------->Message
一个论坛Forum中有多个Thread ,一个Thread 有多个回帖和跟帖。
根据这个层次结构,forum 增加回帖的功能有点太宽了,这个功能应该属于Thread的
代码重构
#import "Thread.h"
@protocol ForumNew<NSObject>
-(void)addThread:(id<NSObject>)thread;
@end
#import "ForumNew.h"
@interface ForumNewObject : NSObject<ForumNew>
@end
#import "ForumNewObject.h"
@interface ForumNewObject()
@property (nonatomic,strong) id<Thread>thread;
@end
@implementation ForumNewObject-
(void)addThread:(id<Thread>)thread{
self.thread = thread;
}
@end
测试代码
id<ForumNew> forumObject= [ForumNewObject new];
ThreadObject * threadobject = [ThreadObject new];
threadobject.name =@"单一职责原则";
[forumObject addThread:threadobject];
[threadobject addReplayMessage];
结果
2018-04-04 14:50:13.955267+0800 设计模式原则[93059:6286368] 单一职责原则 回复信息
其实重构代码就是讲回帖的功能从forum拿到了Thread中,代码很简单,就是需要体会下。
上述问题产生的原因是因为当时我们把论坛的Thread和Message都当做论坛的一个功能。由于后期业务变更,导致Thread 和Message 需要划分为更细致。
如何识别SRP被破坏?
类有太多依赖
类的构造器有太多参数,意味着测试有太多依赖,需要制造mock太多测试输入参数,通常意味着已经破坏SRP了。
方法有太多参数
类似类的构造器,方法参数意味着依赖。
测试类变得复杂
如果测试有太多变量,意味着这个类有太多职责。
类或方法太长
如果方法太长,意味着内容太多,职责过多。
一个类不超过 200-250
描述性名称
如果你需要描述你的类 方法或包,比如使用"xxx和xxx"这种语句,意味着可能破坏了SRP.
低聚合Cohesion的类
聚合Cohesion是一个很重要的概念,虽然聚合是有关结构概念,但是聚合和SRP非常相关,如前面论坛案例,如果一个类不代表一个高聚合,意味着低凝聚low Cohesion,它就可能意味破坏SRP。一个低凝聚的特点:
一个类有两个字段,其中一个字段被一些方法使用;另外一个字段被其他方法使用。
在一个地方改动影响另外一个地方
如果在一个代码地方加入新功能或只是简单重构,却影响了其他不相关的地方,意味着这个地方代码可能破坏了SRP.
猎枪效果Shotgun Effect
如果一个小的改变引起一发动全身,这意味SRP被破坏了。
不能够封装模块
比如使用Spring框架,你使用@Configuration or XML 配置,如果你不能在一个配置中封装一个Bean。意味着它有太多职责,Spring配置应该隐藏内部bean,暴露最少接口,如果你因为多个原因需要改变Spring配置,可能破坏了SRP.
借鉴博客
下一篇博客