“分离职责”是常用的一个重构策略,当一个类担任的职责太多时,应按职责将它拆分红多个类,每一个类分别承担“单一”的职责,也就是让每一个类专心地作“一件事情”。html
在面向对象编程中,SRP原则是一个很是重要的原则(SOLID原则都很重要),在展现示例前,咱们先了解一下SRP原则是什么,以及它有什么做用。编程
SRP原则的定义是这样的:ide
There should never be more than one reason for a class to change.this
就一个类而言,应该仅有一个引发它变化的缘由。spa
When a class has more than one responsibility, there are also more triggers and reasons to change that class. A responsibility is the same as “a reason for change” in this context.设计
由于每个职责都是变化的因子,当需求变化时,该变化一般反映为类的职责的变化。
若是一个类承担了多于一个的职责,那么就意味着引发它的变化的缘由会有多个,等同于把这些职责耦合在了一块儿。
一个职责的变化可能会抑制到该类完成其余职责的能力,这样的耦合会致使脆弱的设计。htm
在设计类或接口时,若是可以遵照SRP原则,则会带来如下优势:对象
Separating responsibility can be done by defining for every responsibility a class or an interface.blog
应该为每一项职责定义类或接口的方式实现职责分离。接口
SRP原则堪称是SOLID原则里面最简单的一个原则,但也能够说是最难的一个。
它的“简单”之处在于它很容易被理解,“困难”之处在于不少人在软件设计过程当中,很难真正地抓住关键点。
对于开发者来讲,“分离职责”存在四个难点,也是开发者在使用这种重构策略时须要慎重考虑的地方
“职责”单一是相对的,每一个人看事情的角度,对业务的理解程度是不尽相同的,这致使了人们对职责的定义和细化程度的差别性。一样一个业务,有些人从角度A出发,在对业务提炼概括总结后,得出三项职责:J、K、L。而有些人则从角度B出发,概括总结出两项职责:X、Y。
在设计接口时,这两人天然而然地会设计出不一样的接口,两人设计的接口个数和表达的语义也各不相同。
拿装修房子来讲,业主一般会委托装修公司来作这件事儿,站在业主的角度理解,它就是一件大事儿——“装修公司装修房子”,至于怎么装修,由装修公司来搞定。
装修公司一般会将这件大事儿拆分红几件小事儿,譬如:“室内设计”、“贴瓷砖”、“作家具”、“刷墙”等等,而后再去雇佣不一样类型的工人来完成这些小事儿。
职责和类的命名应该匹配,若是在职责概括时,概括出的职责比较模糊,可能会使类的命名变得艰难。
另外,即便你概括出的职责是清晰的,若是命名与职责不符(词不达意),仍然会给未来的维护、再重构带来一些困难(命名是很是很是重要的)。
将多个职责被拆分到多个类时,本来在一个类中体现的职责被分散到多个类了,与此同时也须要考虑类的粒度。
粒度应当适中,粒度的控制没有固定的标准,这须要结合业务场景具体分析。
本来用一个类就能完成的功能,如今须要结合多个类才能完成。
如今为了确保原有的功能仍然能正常运行,较大可能会造成多个类之间的依赖关系。
若是有些类被迁移到其余工程了,这还会涉及到工程之间的依赖关系。
“分离职责”是比较难的一个重构策略,尤为是在一些大型项目中。
该策略若是不能良好地利用,可能会让你的工程或解决方案变得不三不四。
若是你的重构经验较浅,建议你从一些较小的项目练习这项重构策略。
这段代码包含两个类Video和Customer。
Viedo类包含三个职责:支付费用、租借Video和计算租金。
Video的职责太多,它把Customer类的职责也“抢”过来了。
public class Video { public void PayFee(decimal fee) { } public void RendVideo(Video video, Customer customer) { customer.Videos.Add(video); } public decimal CalculateBalance(Customer customer) { return customer.LateFees.Sum(); } } public class Customer { public IList<decimal> LateFees { get; set; } public IList<Video> Videos { get; set; } }
在概括职责时,咱们能够经过识别主语的方式来肯定其归属。
因此,咱们能够将Video类的PayFee()、CalculateBalance()方法放到Customer类中。
public class Video { public void RentVideo(Video video, Customer customer) { customer.Videos.Add(video); } } public class Customer { public IList<decimal> LateFees { get; set; } public IList<Video> Videos { get; set; } public void PayFee(decimal fee) { } public decimal CalculateBalance(Customer customer) { return customer.LateFees.Sum(); } }
原则就像基准线,在设计类和接口时,咱们应该尽可能遵照基准线,而不是死守基准线,在设计时不该死板地依照原则进行设计。这就比如开车,司机的视线应该始终保持在正前方,若是沿着公路上的线开车而忽视了前方的交通状况,可能会引起交通事故。