让姑姑不再划拳 码农也要有原则 : SOLID via C#
“姑娘,别这样。我们是有原则的。”
“一个有原则的程序猿是不会写出 “摧毁地球” 这样的程序的,他们会写一个函数叫 “摧毁行星”而把地球当一个参数传进去。”
“对,是时候和那些只会滚键盘的麻瓜不同了,我们可是有高逼格的程序猿。”
[小九的学堂,致力于以平凡的语言描述不平凡的技术。如要转载,请注明来源:小九的学堂。cnblogs.com/xfuture]
写代码其实就是给一个世界创造秩序,世界越大就越需要原则,各司其职,统筹合作自然是比无脑的积木堆积星球来的令人信服,部分基于SOLID architecture principles using simple C# examples。
下面来用C#语言展现面向对象最佳实践的SOLID软件设计原则。
目录
- 何为SOLID?
- S:SRP, Single Responsibility Principle, 单一责任原则
- O:OCP, Open Closed Principle, 开发封闭原则
- L: LSP, Liskov Substitution Principle, 里氏替换原则
- I:ISP, Interface Segregation Principle, 接口分离原则
- D: DIP, Dependency Inversion Principle, 依赖倒置原则
- 总结
何为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#例子来一一介绍。
S:SRP, 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());
}
}
}
相信大家都发现这个问题出在哪里了,一个顾客类竟然可以自主写log!Customer 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?msg=4729987#xx4729987xx
O: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 == )
{
return TotalSales - ;
}
else
{
return TotalSales - ;
}
}
}
“嫌疑人”出现了,当我们再添加一个用户类型时,我们还需要添加修改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) - ;
}
} class goldCustomer : SilverCustomer
{
public override double getDiscount(double TotalSales)
{
return base.getDiscount(TotalSales) - ;
}
}
这样也就解决了多次修改带来的问题,通过扩展基类,而不是修改。
OCP原则 拥抱扩展,拒绝修改,保证了现有逻辑的稳定性。
其实还有一张比较XXX的图来表示OCP,我这边就不镶嵌到文章里了,因为....好奇的小盆友可以戳戳,记得留言写下感悟...:戳我
L: LSP Liskov Substitution Principle 里氏替换原则
跨过前两个坎,现在我们来到了第三个原则,这次我们换一个模型。首先我们有一个Bird的Class,有一个Fly的方法:
class Bird
{
public void Fly()
{
// Fly Logic
}
}
后来我们发现生物学上企鹅也属于鸟类,当然这只企鹅不在深圳,但它不会飞。
于是我们设计一个Penguin的class 继承自Bird 重写其Fly方法,标明其不会飞
class Penguin: Bird
{
public override void Fly()
{
throw new Exception("Can Not Fly");
}
}
眨眼一看是没有问题,但实际上问题可大了去了,于是,
好吧,大家来找茬又开始了,这次的茬可是隐藏的很深。
首先在程序开发中,你并不能保证你继承的父类重写了的方法是安全的,它里面可能包含其他逻辑。而且在后续的开发中,极容易出现下面的代码:
List<Bird> Birds = new List<Bird>();
// Add Bird Logic
foreact(var bird in Birds)
{
bird.Fly();
}
这时企鹅君便要崩溃了。风险就在别人使用你设计的类的时候并没有想到并非所有的子类都符合父类的要求。这便不符合LSP的原则了。
LSP俗语是:“老鼠的儿子会打洞。”,其实就是子类是和父类有相同的行为和状态,是可以完全替换父类的。这也是保护了OCP的开放扩展关闭修改的原则。
前面例子更改方法可以采用父类高聚合,子类低耦合的原则来做,父类要精,子类可以采用接口实现的方法来进行扩展。
比如可以增加一个IFly的接口,实现该接口的子类便可以飞,而父类中便不再包含Fly方法。
还有其他方法,大家可以扩散思想回复在下面。
I:ISP Interface Segregation Principle 接口分离原则
还是企鹅和鸟的问题,我们现在有个鸟类接口:
interface IBird
{
void Fly();
void Eat(); }
燕子实现了Fly和Eat,而企鹅只能实现Eat,所以代码如下:
class Swallow : IBird
{
public void Fly()
{
// Fly Logic
} public void Eat()
{
// Eat
}
} class Penguin : IBird
{
public void Fly()
{
//No Implementation
}
public void Eat()
{
// Eat
}
}
如果我们保持这个设计,在什么场景会出现问题呢?
好了!5!4!3!2!1!
揭晓答案!(这个流程会不会太综艺化..=。=)
企鹅继承了IBird的接口,就必须实现其所以方法,虽然它在飞的方法里什么都没做。负面效果就是在统计会飞的鸟儿的时候,因为企鹅也实现了其Fly方法,但也会被归纳其中。
其实它压根不会飞嘛!!!
所以这样就违反了接口分离规则,接口分离规则旨在使用多个特定功能的接口来避开通用接口造成的富余化。
也就是说 我们可以分离IBird为IFly和IEat,Swallow实现IFly和IEat,而呆呆的企鹅只需要实现IEat就好。
这样让程序的设计更加灵活,当需求改变的时候,我们要做的工作便小很多也风险少很多,也会发现更多的乐趣。
D:DIP, Dependency Inversion Principle, 依赖倒置原则
依赖倒置原则在SOLID里是我认为最为出彩的原则和技术。简单来说就是面向接口编程。
之前博客中已有文来介绍其所以然,这里引用来:http://www.cnblogs.com/xfuture/p/3682666.html ,下面做一下简单的介绍,也算是...大片预告片?...
远古母系氏族,每个人都是一个独立的个体,需要什么工具就需要自己去打磨一件工具,自己需要了解所有的流程才能生存。比如打猎,从前期准备绳索,尖木,到中期做陷阱,后期收成,都需要了解的非常透彻。对应编程中便是new 绳索(),new 尖木(),new 陷阱(),new X()。实例化所有需要的资源,然后再进行逻辑流程。
人类逐渐在进步,工业革命的来袭,改变了整个社会的结构。人再不需要了解所有的流程,只需要去一个工厂或者采购平台,输入自己想要的东西,便能得到。对应编程中便是工厂模式,需要一个静态工厂类,一个抽象产品类型的类,一个你想拿到的可以具象化的产品类,从此便进入了全民淘宝年代,需要什么购买什么。
当你为了修一个顶楼的灯泡购买了梯子,但当修好后,如何处理这个梯子便成了难题,扔掉不舍,不扔去卖二手又很麻烦。这时候就需要我们的主角:和谐社会登场了!主张不铺张不浪费,这便是一种回收机制,你需要它只需要说一声,秒秒钟就到你手里,你也不需要知道他来自哪里。不需要了你也不用管,我直接秒秒钟再变走。是不是有一种魔术的感觉?这便是依赖注入!依赖注入解除了对象和对象的依赖关系,需要其他对象,会有外部直接注入,而你自己不需要关心他如何而来。对象符合OCP原则(对外扩展开放,对修改关闭), 容器负责所有关系匹配。在此层,容器便是这个社会的规则,而对象只需要关心自己所完成的一部分。轻松惬意!
总结
Shivprasad koirala 用一句话总结的SOLID总结的很好,这里就不翻译了,大家来感受下大牛对SOLID精确的理解:
S:SRP, A class should take care of only one responsibility.
O: OCP, Extension should be preferred over modification.
L: LSP, A parent class object should be able to refer child objects seamlessly during runtime polymorphism.
I: ISP, Client should not be forced to use a interface if it does not need it.
D: DIP, High level modules should not depend on low level modules but should depend on abstraction.
呼,希望大家喜欢文章风格,也希望能批评指正。
另有WPF2000Tips系列,大家感兴趣可以点点左边WPF标签,里有系列文。
让姑姑不再划拳 码农也要有原则 : SOLID via C#的更多相关文章
- 关于期权池Option Pools与Vesting:码农创业防身必备法器
之前又看到饿了么创始人团队纠纷的几篇文章,参考了百科.wiki.36Kr.虎嗅.知乎以及邵亦波老师的文章,对之前一直感兴趣的期权汇编初略总结了下 ,仍觉粗糙,对一些具体操作还是不甚了了,不过感觉在中国 ...
- 管理与技术未必不可兼得,一个20年IT老兵的码农生涯
作者|康德胜 我是一个喜欢写代码但几乎不太有机会写代码的CTO,也是一个看得懂财务报表.通过所有CFA(金融特许分析师)考试并获得FRM(金融风险经理)认证的拿到金融MBA的CTO,如果我有幸被称作码 ...
- 码农-->工程师
微信公众号推送文章记录,侵删 一个猎人的比喻: 当土著拿到猎枪之后,他们射箭的技能退化严重,但因为食物更多了,厨艺有了长足的进展. 当你不再为一些问题担心之后,你就可以把注意力集中在另外一些问题上了. ...
- PAT 1046. 划拳(15)
划拳是古老中国酒文化的一个有趣的组成部分.酒桌上两人划拳的方法为:每人口中喊出一个数字,同时用手比划出一个数字.如果谁比划出的数字正好等于两人喊出的数字之和,谁就赢了,输家罚一杯酒.两人同赢或两人同输 ...
- 【整理】待毕业.Net码农就业求职储备
声明:本文题目来源于互联网,仅供即将从学校毕业的.Net码农(当然,我本人也是菜逼一个)学习之用.当然,学习了这些题目不一定会拿到offer,但是针对就业求职做些针对性的准备也是不错的.此外,除了技术 ...
- <开心一笑> 码农 黑客和2B程序员之间的区别
笔记本电脑 码农: 黑客: 2B程序员: 求2的32次方: 码农: System.out.println(Math.pow(2, 32)); 黑客: System.out.println(1L< ...
- 经典算法C++版(参考一线码农博文)
鉴于一线码农的算法博文基本通过C#完成,此处用C++再实现一遍,具体解法可参考其博文. 地址:http://www.cnblogs.com/huangxincheng/category/401959. ...
- [2013 eoe移动开发者大会]靳岩:从码农到极客的升级之路
(国内知名Android开发论坛 eoe开发者社区推荐:http://www.eoeandroid.com/) 前天,2013 eoe 移动开发者大会在国家会议中心召开,eoe 开发者社区创始人靳岩在 ...
- 专门为码农定制的14款创意的T裇(T-Shirt)设计
T裇衫是人们在各种场合都可穿着的服装,如在T裇衫上作适当的装饰,即可增添无穷的韵味.通过图案直接反映人类的精神风貌,你可以把日常生活中的兴趣.习惯.喜怒哀乐.嗜好等展露无疑,张扬个性.秀出自我.对于码 ...
随机推荐
- MIP改造常见问题二十问
在MIP推出后,我们收到了很多站长的疑问和顾虑.我们将所有疑问和顾虑归纳为以下二十个问题,希望对大家理解 MIP 有帮助. 1.MIP 化后对其他搜索引擎抓取收录以及 SEO 的影响如何? 答:在原页 ...
- CSS HTML元素布局及Display属性
本篇文章主要介绍HTML的内联元素.块级元素的分类与布局,以及dispaly属性对布局的影响. 目录 1. HTML 元素分类:介绍内联元素.块级元素的分类. 2. HTML 元素布局:介绍内联元素. ...
- Hawk 6. 高级话题:子流程系统
子流程的定义 当流程设计的越来越复杂,越来越长时,就难以进行管理了.因此,采用模块化的设计才会更加合理.本节我们介绍子流程的原理和使用. 所谓子流程,就是能先构造出一个流程,然后被其他流程调用.被调用 ...
- Spring的数据库开发
Spring JDBC框架操作mysql数据库 Spring中的JDBC为我们省去连接和关闭数据库的代码,我们着重关注对数据库的操作.Sprin ...
- win7下利用ftp实现华为路由器的上传和下载
win7下利用ftp实现华为路由器的上传和下载 1. Win7下ftp的安装和配置 (1)开始->控制面板->程序->程序和功能->打开或关闭Windows功能 (2)在Wi ...
- swift开发新项目总结
新项目用swift3.0开发,现在基本一个月,来总结一下遇到的问题及解决方案 1,在确定新项目用swift后,第一个考虑的问题是用纯swift呢?还是用swift跟OC混编 考虑到新项目 ...
- 跟着老男孩教育学Python开发【第一篇】:初识Python
Python简介 Python前世今生 Python的创始人为吉多·范罗苏姆(Guido van Rossum).1989年的圣诞节期间,吉多·范罗苏姆为了在阿姆斯特丹打发时间,决心开发一个新的脚本解 ...
- centos6和centos7防火墙的关闭
CentOS6.5查看防火墙的状态: [zh@localhost ~]$service iptable status 显示结果: [zh@localhost ~]$service iptable st ...
- 端盘子的服务生到月薪一万五的IT精英,你能相信吗
一直以来,我都觉得自己不是一个有故事的人. 以前的我,是个乖宝宝,对父母言听计从,特别内向,甚至一度感觉到自卑.不上学之后,我干过送货员,去工地除泥搬砖,当过油漆工,去过工厂,还去饭店当过端盘子的服务 ...
- Spark Streaming+Kafka
Spark Streaming+Kafka 前言 在WeTest舆情项目中,需要对每天千万级的游戏评论信息进行词频统计,在生产者一端,我们将数据按照每天的拉取时间存入了Kafka当中,而在消费者一端, ...