前言:上篇C#软件设计——小话设计模式原则之:依赖倒置原则DIP简单介绍了下依赖倒置的由来以及使用,中间插了两篇WebApi的文章,这篇仍是回归正题,继续来写写设计模式另外一个重要的原则:单一职责原则。html
软件设计原则系列文章索引设计模式
单一职责原则,英文缩写SRP,全称Single Responsibility Principle。post
原始定义:There should never be more than one reason for a class to change。url
官方翻译:应该有且仅有一个缘由引发类的变动。简单点说,一个类,最好只负责一件事,只有一个引发它变化的缘由。spa
上面的定义不难理解,引发类变化的缘由不能多于一个。也就是说每个类只负责本身的事情,此所谓单一职责。翻译
咱们知道,在OOP里面,高内聚、低耦合是软件设计追求的目标,而单一职责原则能够看作是高内聚、低耦合的引伸,将职责定义为引发变化的缘由,以提升内聚性,以此来减小引发变化的缘由。职责过多,可能引发变化的缘由就越多,这将是致使职责依赖,相互之间就产生影响,从而极大的损伤其内聚性和耦合度。单一职责一般意味着单一的功能,所以不要为类实现过多的功能点,以保证明体只有一个引发它变化的缘由。设计
不论是从官方定义,仍是对“单一职责”名称的解释,都能很好的理解单一职责原则的意义。其实在软件设计中,要真正用好单一职责原则并不简单,由于遵循这一原则最关键的地方在于职责的划分,博主的理解是职责的划分是根据需求定的,同一个类(接口)的设计,在不一样的需求里面,可能职责的划分并不同,为何这么说呢?咱们来看下面的例子。code
关于单一职责原则的原理,咱们就不作过多的解释了。重点是职责的划分!重点是职责的划分!重点是职责的划分!重要的事情说三遍。下面根据一个示例场景来看看如何划分职责。htm
假定如今有以下场景:国际手机运营商那里定义了生产手机必需要实现的接口,接口里面定义了一些手机的属性和行为,手机生产商若是要生成手机,必需要实现这些接口。blog
咱们首先以手机做为单一职责去设计接口,方案以下。
/// <summary> /// 充电电源 /// </summary> public class ElectricSource { }
public interface IMobilePhone { //运行内存 string RAM { get; set; } //手机存储内存 string ROM { get; set; } //CPU主频 string CPU { get; set; } //屏幕大小 int Size { get; set; } //手机充电接口 void Charging(ElectricSource oElectricsource); //打电话 void RingUp(); //接电话 void ReceiveUp(); //上网 void SurfInternet(); }
而后咱们的手机生产商去实现这些接口
//具体的手机示例 public class MobilePhone:IMobilePhone { public string RAM { get {throw new NotImplementedException();} set{ throw new NotImplementedException();} } public string ROM { get{throw new NotImplementedException();} set{ throw new NotImplementedException();} } public string CPU { get{ throw new NotImplementedException();} set{ throw new NotImplementedException();} } public int Size { get{throw new NotImplementedException();} set{throw new NotImplementedException();} } public void Charging(ElectricSource oElectricsource) { throw new NotImplementedException(); } public void RingUp() { throw new NotImplementedException(); } public void ReceiveUp() { throw new NotImplementedException(); } public void SurfInternet() { throw new NotImplementedException(); } }
这种设计有没有问题呢?这是一个颇有争议的话题。单一职责原则要求一个接口或类只有一个缘由引发变化,也就是一个接口或类只有一个职责,它就负责一件事情,原则上来讲,咱们以手机做为单一职责去设计,也是有必定的道理的,由于咱们接口里面都是定义的手机相关属性和行为,引发接口变化的缘由只多是手机的属性或者行为发生变化,从这方面考虑,这种设计是有它的合理性的,若是你能保证需求不会变化或者变化的可能性比较小,那么这种设计就是合理的。但实际状况咱们知道,现代科技突飞猛进,科技的进步促使着人们不断在手机原有基础上增长新的属性和功能。好比有一天,咱们给手机增长了摄像头,那么须要新增一个像素的属性,咱们的接口和实现就得改吧,又有一天,咱们增长移动办公的功能,那么咱们的接口实现是否是也得改。因为上面的设计没有细化到必定的粒度,致使任何一个细小的改动都会引发从上到下的变化,有一种“牵一发而动全身”的感受。因此须要细化粒度,下面来看看咱们如何变动设计。
咱们将接口细化
//手机属性接口 public interface IMobilePhoneProperty { //运行内存 string RAM { get; set; } //手机存储内存 string ROM { get; set; } //CPU主频 string CPU { get; set; } //屏幕大小 int Size { get; set; } //摄像头像素 string Pixel { get; set; } } //手机功能接口 public interface IMobilePhoneFunction { //手机充电接口 void Charging(ElectricSource oElectricsource); //打电话 void RingUp(); //接电话 void ReceiveUp(); //上网 void SurfInternet(); //移动办公 void MobileOA(); }
实现类
//手机属性实现类 public class MobileProperty:IMobilePhoneProperty { public string RAM { get{ throw new NotImplementedException();} set{ throw new NotImplementedException();} } public string ROM { get{ throw new NotImplementedException();} set{ throw new NotImplementedException();} } public string CPU { get{ throw new NotImplementedException();} set{throw new NotImplementedException();} } public int Size { get{throw new NotImplementedException();} set{throw new NotImplementedException();} } public string Pixel { get{throw new NotImplementedException();} set{throw new NotImplementedException();} } } //手机功能实现类 public class MobileFunction:IMobilePhoneFunction { public void Charging(ElectricSource oElectricsource) { throw new NotImplementedException(); } public void RingUp() { throw new NotImplementedException(); } public void ReceiveUp() { throw new NotImplementedException(); } public void SurfInternet() { throw new NotImplementedException(); } public void MobileOA() { throw new NotImplementedException(); } } //具体的手机实例 public class HuaweiMobile { private IMobilePhoneProperty m_Property; private IMobilePhoneFunction m_Func; public HuaweiMobile(IMobilePhoneProperty oProperty, IMobilePhoneFunction oFunc) { m_Property = oProperty; m_Func = oFunc; } }
对于上面题的问题,这种设计可以比较方便的解决,若是是增长属性,只须要修改IMobilePhoneProperty和MobileProperty便可;若是是增长功能,只须要修改IMobilePhoneFunction和MobileFunction便可。貌似完胜第一种解决方案。那么是否这种解决方案就完美了呢?答案仍是看状况。原则上,咱们将手机的属性和功能分开了,使得职责更加明确,全部的属性都由IMobilePhoneProperty接口负责,全部的功能都由IMobilePhoneFunction接口负责,若是是需求的粒度仅仅到了属性和功能这一级,这种设计确实是比较好的。反之,若是粒度再细小一些呢,那咱们这种职责划分是否完美呢?好比咱们普通的老人机只须要一些最基础的功能,好比它只须要充电、打电话、接电话的功能,可是按照上面的设计,它也要实现IMobilePhoneFunction接口,某一天,咱们增长了一个新的功能玩游戏,那么咱们就须要在接口上面增长一个方法PlayGame()。但是咱们老人机根本用不着实现这个功能,但是因为它实现了该接口,它的内部实现也得从新去写。从这点来讲,以上的设计仍是存在它的问题。那么,咱们如何继续细化接口粒度呢?
接口细化粒度设计以下
//手机基础属性接口 public interface IMobilePhoneBaseProperty { //运行内存 string RAM { get; set; } //手机存储内存 string ROM { get; set; } //CPU主频 string CPU { get; set; } //屏幕大小 int Size { get; set; } } //手机扩展属性接口 public interface IMobilePhoneExtentionProperty { //摄像头像素 string Pixel { get; set; } } //手机基础功能接口 public interface IMobilePhoneBaseFunc { //手机充电接口 void Charging(ElectricSource oElectricsource); //打电话 void RingUp(); //接电话 void ReceiveUp(); } //手机扩展功能接口 public interface IMobilePhoneExtentionFunc { //上网 void SurfInternet(); //移动办公 void MobileOA(); //玩游戏 void PlayGame(); }
实现类和上面相似
//手机基础属性实现 public class MobilePhoneBaseProperty : IMobilePhoneBaseProperty { public string RAM { get{throw new NotImplementedException();} set{throw new NotImplementedException();} } public string ROM { get{throw new NotImplementedException();} set {throw new NotImplementedException();} } public string CPU { get{throw new NotImplementedException();} set{ throw new NotImplementedException();} } public int Size { get{ throw new NotImplementedException();} set{ throw new NotImplementedException();} } } //手机扩展属性实现 public class MobilePhoneExtentionProperty : IMobilePhoneExtentionProperty { public string Pixel { get{ throw new NotImplementedException();} set{ throw new NotImplementedException();} } } //手机基础功能实现 public class MobilePhoneBaseFunc : IMobilePhoneBaseFunc { public void Charging(ElectricSource oElectricsource) { throw new NotImplementedException(); } public void RingUp() { throw new NotImplementedException(); } public void ReceiveUp() { throw new NotImplementedException(); } } //手机扩展功能实现 public class MobilePhoneExtentionFunc : IMobilePhoneExtentionFunc { public void SurfInternet() { throw new NotImplementedException(); } public void MobileOA() { throw new NotImplementedException(); } public void PlayGame() { throw new NotImplementedException(); } }
此种设计能解决上述问题,细分到此粒度,这种方案基本算比较完善了。能不能算完美?这个得另说。接口的粒度要设计到哪一步,取决于需求的变动程度,或者说取决于需求的复杂度。
以上经过一个应用场景简单介绍了下单一职责原则的使用,上面三种设计,没有最合理,只有最合适。理解单一职责原则,最重要的就是理解职责的划分,职责划分的粒度取决于需求的粒度,最后又回到了那句话:没有最好的设计,只有最适合的设计。欢迎园友拍砖斧正。若是园友们以为本文对你有帮助,请帮忙推荐,博主将继续努力~~