让姑姑不再划拳 码农也要有原则 : SOLID via C# 何为SOLID?S.O.L.I.D.是一组面对面向对象设计的最佳实践的设计原则。术语来自Robert C.Martin的著作Agile Principles, Patterns, and Practices in C#代表了下面五个设计原则1. SRP(Single Responsibility Principle) 单一责任原则2. OCP(Open Closed Principle) 开放封闭原则3. LSP(Liskov Substitution Principle) 里氏替换原则4. ISP(Interface Segregation Principle) 接口分离原则5. DIP(Dependency Inversion Principle) 依赖倒置原则下面用C#例子来一一介绍。SSRP, Single Responsibility Principle, 单一责任原则人类学习和理解最快的方式是实践这点在编程上显得尤为突出。理解SOLID最好的方式就是先去了解它解决了什么问题。首先给大家出一道大家来找茬下面这段代码中有一个很大的问题你找到了吗(停停停不用去倒杯茶细细来看因为这段代码已经简单到没朋友了)那我们现在就抛开和华生的基情对这个作案现场来调查一番。class Customer { public void Add() { try { // Database code goes here } catch (Exception ex) { System.IO.File.WriteAllText(c:\Error.txt, ex.ToString()); } } }相信大家都发现这个问题出在哪里了一个顾客类竟然可以自主写logCustomer Class 应该是要做关于Customer Datavalidation或者访问顾客相关的数据库进行存储的相关操作实现Log的记录实际上已经超出了其责任的范围。这就像小龙女不去做个安静的美姑姑而去学划拳和人斗酒一样WTF当明日需要你改造Log记录的实现或路径的时候而你却push了一段Customer类的改动这会让人感到非常奇怪的。这也让我想起来了一个世界知名的工具-瑞士军刀。毫无疑问它很棒但当你需要改动其中一个部分的时候其余部分要一起重新来排列保证不会互相干扰到。而且你可以尝试一个场景用瑞士军刀掏耳朵那种感觉真的是醉了。倒不如我们一一拆分各司其职剪子剪纸耳勺掏耳使部件功能简单化互不影响。这个原则适用于软件架构中类和对象的设计。所以简而言之SRP就是指单个类应该有且仅有单个职能。所以我们可以对刚才案例朝这个目标进行初步改造首先将记录log的逻辑在一个单独的FileLogger类上实现class FileLogger { public void Handle(string error) { System.IO.File.WriteAllText(c:\Error.txt, error); } }现在Customer类可以欢快的抛弃“五魁首六六六”FileLogger class 来负责记录log的具体实现而customer class可以更专注的负责自己的模块。class Customer { private FileLogger obj new FileLogger(); publicvirtual void Add() { try { // Database code goes here } catch (Exception ex) { obj.Handle(ex.ToString()); } } }如果有一些SRP经验的朋友可能已经发现其实这种解决方案并不能完全解决SRP的问题。因为try catch其实并不是Customer类需要关心的功能。在记录Log这一层不同的语言和结构都会有一个类似Asp.Net中Global.asax或者WPF中App.xaml.cs这类文件可以集中来处理这些冒泡的错误这样Customer类中便不会有TryCatch的方法。其实这个程序依然可以更好也可以有更多的解决方案但此文旨在使用足够简单的例子来用C#阐述SOLID也希望可以不禁锢大家思维有好的方案可以在下面回复和交流来产出一个伟大的解决方案。在codeproject里有一个答案是很不错的具体实现就不剧透了如感兴趣可以戳http://www.codeproject.com/Articles/703634/SOLID-architecture-principles-using-simple-Csharp?msg4729987#xx4729987xxO:OCP, Open Closed Principle 开放封闭原则上一个“场景”过了SRP阶段我们要继续开始OCP阶段了, OCP简单来说就是 对扩展是开放的对修改是封闭的。在Customer类中我们现在添加一个属性来表示他是黄金用户还是银色用户。当CustType为1时为Gold用户为2时为Silver用户.根据用户类型不同来返回不同的折扣。来继续来找茬了这个节奏好像看起来大家看完本文后能在大家来找茬中无往不胜啊haha。开启福尔摩斯模式关注在getDiscount方法中的if语句class Customer { private int _CustType; public int CustType { get { return _CustType; } set { _CustType value; } } public double getDiscount(double TotalSales) { if (_CustType 1) { return TotalSales - 100; } else { return TotalSales - 50; } } }“嫌疑人”出现了当我们再添加一个用户类型时我们还需要添加修改if中的折扣逻辑也就是我们需要修改Customer Class。当我们一次次更改Customer Class我们还需要确认之前的逻辑是没错的以及引用该Class的更多的逻辑也是没问题的也就说需要一次又一次的测试。那么问题来了挖掘...不对是如何来避免多次的“Modify”而带来的恶果呢那就是“EXTENSION”(扩展).当我们每次增加一个用户类型的时候我们就增加一个Customer的扩展类因此我们也就每次只需要测试新加的类。class Customer { public virtual double getDiscount(double TotalSales) { return TotalSales; } } class SilverCustomer : Customer { public override double getDiscount(double TotalSales) { return base.getDiscount(TotalSales) - 50; } } class goldCustomer : SilverCustomer { public override double getDiscount(double TotalSales) { return base.getDiscount(TotalSales) - 100; } }这样也就解决了多次修改带来的问题通过扩展基类而不是修改。OCP原则 拥抱扩展拒绝修改保证了现有逻辑的稳定性。其实还有一张比较XXX的图来表示OCP我这边就不镶嵌到文章里了因为....好奇的小盆友可以戳戳记得留言写下感悟...戳我L: LSP Liskov Substitution Principle 里氏替换原则跨过前两个坎现在我们来到了第三个原则这次我们换一个模型。首先我们有一个Bird的Class,有一个Fly的方法class Bird { public void Fly() { // Fly Logic } }后来我们发现生物学上企鹅也属于鸟类当然这只企鹅不在深圳但它不会飞。