1、单一职责
定义:不要存在多于一个导致类变更的原因。通俗的说,即一个程序集、一个类、一个接口、一个方法 最优的设计原则是只负责一项职责。
核心精神:一个程序集、一个类,或者一个接口,一个方法、最好只做一件事情,当发生变化时,他只能受到单一的影响;因为职责越多,可能引起变化的原因将会越多,这样导致职责和功能上的依赖,将严重影响其内聚性和耦合度,混乱由此而生。
方法职责
混合方法:
public void WaterMark(string PicturePath, string Word) { FileInfo fileInfo = new FileInfo(PicturePath); switch (fileInfo.Extension) { case "jpg": ///jpg 处理代码 break; case "jpep": ///jpep 处理代码 break; default: break; } }
单一职责的方法:
public void WaterMarkJpg(string PicturePath, string Word) { FileInfo fileInfo = new FileInfo(PicturePath); ///jpg 处理代码 } public void WaterMarkJpep(string PicturePath, string Word) { FileInfo fileInfo = new FileInfo(PicturePath); ///jpeg 处理代码 }
这两种方法有什么优缺点呢?
混合方法:
缺点:修改、增加、如果有错误、会影响到其它全部的职责使用。
优点:扩展的时候、只需要修改方法,调用端不用更改代码。封闭了变化点。
单一职责方法:
优点:增加、修改的时候,只会影响到自己、不会影响到别的职责。
缺点:调用端增加相对应的修改,变化点传递给使用端。
以上对于方法的职责分析中、各有优缺点,使用哪一种方式最好,很难讲。
类职责
类也应该遵守单一职责、
混合职责类,上传和下载
public class UploadAndDownLoad { /// <summary> /// 文件上传 /// </summary> /// <param name="file"></param> /// <param name="path"></param> public static void Upload(HttpPostedFile file ,string path) { file.SaveAs(path); } /// <summary> /// 下载文件 /// </summary> /// <param name="path"></param> /// <returns></returns> public static FileStream DownLoad(string path) { return File.OpenRead(path); } }
如果有一天我们的需求发生变更,我们需要把上传类改成这样,
public class UploadAndDownLoad<S> where S:Stream,new() { /// <summary> /// 文件上传 /// </summary> /// <param name="file"></param> /// <param name="path"></param> public static void Upload(string path,S stream) { byte[] bufeel = new byte[stream.Length]; stream.Read(bufeel, 0, 10000); File.WriteAllBytes(path, bufeel); } /// <summary> /// 下载文件 /// </summary> /// <param name="path"></param> /// <returns></returns> public static FileStream DownLoad(string path) { return File.OpenRead(path); } }
你会发现,下载的调用代码也必须要修改。所以类型一定要在设计的时候遵行单一职责。这样我在修改上传类的功能,不会影响到下载类的功能调用。
程序集:
程序集更应该理顺所有职责,因同样的道理。为了将来的升级维护方便 。
只有你用好了,单一职责,你才会代码清楚、不会产生乱象。这样才可以容易维护。
如果我不分开,只会增加维护难度。
2、里氏替换原则
里氏替换原则通俗的来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能
原因如下:
1、子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
2、子类中可以增加自己特有的方法。(可以扩展自己的功能)
3、当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。
4、当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。
子类和父类继承关于方法重写的2种操作。
重载(Overload):方法名称相同,参数、返回值不同 的一组方法,用来动态处理相适配的功能。
重写(Override):覆盖父类的方法,实体方法可以用 new 来重写,虚方法可以用 override 来重写,抽像方法也可以用 override 来重写。
除非必须、尽可能少地重写父类方法,因为子类也会被继承,这在整个体系中会发生混乱。
3、依赖倒置(上端和下端的依赖要通过口、而不是直接实现)
依赖倒置原则,重要的三层含义:
1、高层模块不应该依赖低层模块,两者都应该依赖其抽象;
2、抽象不应该依赖细节;
3、细节应该依赖抽象。
其核心思想是:依赖于抽象。
应用在项目中,实际就是类与类之间的引用、都应该抽象出来、抽象成接口或者是抽象类。类与类之间的引用必须要基于接口或者是抽象类。而不是直接引用
例如:
直接依赖:
class Mother { public void narrate(Book reader) { Console.WriteLine ("妈妈开始讲故事"); Console.WriteLine(reader.getContent()); } } class Book { public string getContent() { return "很久很久以前有一个阿拉伯的故事……"; } }
依赖倒置:
class Mother { public void narrate(IReader reader) { Console.WriteLine ("妈妈开始讲故事"); Console.WriteLine(reader.getContent()); } } public interface IReader { string getContent(); } class Book: IReader { public string getContent() { return "很久很久以前有一个阿拉伯的故事……"; } }
1、Mother类与Book类并不是直接依赖使用的,而是通过IReader接口来依赖。
2、如果有一天,增加其它读物,比如NEWSPAPER,报纸类。只需要
class NewsPaper : IReader { public string getContent() { return "很久很久以前有一个阿拉伯的故事……"; } }
这就充分符合,开闭原则,对扩展开放,对修改封闭。(我要增加一个新的功能,我只需要增加一个类就OK了,并不需要对原有的类进行修改)
4、接口隔离原则
定义:客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。
(接口也需要单一职责、尽可能把接口的功能分开,最小化设计)也不需要一个方法一个接口、这种过份了。只需要一类功能放在一个接口里面
抽像是稳定的,细节是多变的。接口要稳定
5、迪米特法则
高内聚、自己的事尽量自己完成。
低耦合、我们之间相互依赖尽量减少。
接口统一、功能封闭
只关心自己的职责、只对依赖负责
增加功能是扩展、修改代码是修改。
一个类尽可能少的与外界发生关联。
6、开闭原则
问题由来:在软件的生命周期内,因为变化、升级和维护等原因需要对软件原有代码进行修改时,可能会给旧代码中引入错误,也可能会使我们不得不对整个功能进行重构,并且需要原有代码经过重新测试。
解决方案:当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。
说到这里,再回想一下前面说的5项原则,恰恰是告诉我们用抽象构建框架,用实现扩展细节的注意事项而已:
单一职责原则告诉我们实现类要职责单一;
里氏替换原则告诉我们不要破坏继承体系;
依赖倒置原则告诉我们要面向接口编程;
接口隔离原则告诉我们在设计接口的时候要精简单一;
迪米特法则告诉我们要降低耦合。
而开闭原则是总纲,他告诉我们要对扩展开放,对修改关闭。
参考资料:
http://www.uml.org.cn/sjms/201211023.asp
http://jingyan.baidu.com/article/6525d4b13aed08ac7c2e947f.html