一、前言
项目中使用 Design Pattern 越多越好?
哪种 Pattern 比较重要?
需要学习哪些 Pattern ?
代码是否可测试?
对于入门者来说,直接上手学习 Design Pattern 可能会产生很多困惑,认为编程很难。好比修炼神功秘籍,首先得会基本功晓原理;其次才能进阶修炼高阶术法,除非有高人指点,否则轻则自废武功重则走火入魔。
其实不必, 没有 Design Pattern 也可以写出 “Good Code”。应用程序首先是用来解决问题,不要过分关心 Design Pattern 没有必要为了 Pattern 而 Pattern;但软件开发领域 “Design Pattern” 又很重要,怎么办? 有个建议可以从 “SOLID、DRY” 开始,面向对象编程的基本原则。
面向对象原则 & Design Pattern 两者区别:
面向对象基本原则:是一种指导规范,应该怎么干,告诉开发者你应该遵循这种规范。
Design Pattern :是遵循编程规范解决特定问题的具体实践。
二、面向对象基本原则
SOLID 由美国软件工程师 Robert C. Martin 总结提出 。维基百科 :https://en.wikipedia.org/wiki/SOLID 。
SOLID:
S – <Single-responsiblity Principle> 单一职责原则
O – <Open-closed Principle>开闭原则
L -<Liskov Substitution Principle>里氏替换原则
I – <Interface Segregation Principle>接口隔离原则
D -<Dependency Inversion Principle>依赖倒置原则
DRY: < Don’t repeat yourself> 不要重复自己原则
1、Single-responsiblity Principle
单一职责描述:一个类只有一个用途,没有两个用途,没有三个用途。
好比假设汽车的刹车、油门踏板没有分开设计,被设计成 刹车油门踏板,或者把离合也加进去。想象一下,使用的时候垂直踏下去是刹车,向左偏向下踏是加油,向右向下踏是离合;这样设计的后果 不仅考验驾车者的车技,甚者交通事故率大概率会直线上升。
实践中如果发现类规模越来越大职责越来越多,此时开始着手重构它。
实践中如果发现一个方法,随着业务变动参数越来越多、方法体越来越臃肿、干的事情越来越多,最显而易见的是 If else 语句变多,此时着手重构它。
题外:分析具体情况,参数多是不是也可以重构为使用一个对象传递,If else 语句多使用 “表驱动法” 、Factory Pattern 解决。
2、Open-closed Principle
开闭原则描述:软件实体应该对扩展开放,对修改关闭。
现在各种计步手环特别火,公司安排你做一个集成平台,展示跑步线路。该怎么做?
假设你是一个软件工程师,加班太多以至于没有时间打扫房间,你雇佣了一个王阿姨打扫房间,一直相安无事,突然有一天王阿姨家有事回家了。刚好这一天心仪的女同事提出去你家做客,而你焦虑房间乱没人打扫。 有了这次经验,你可能会找一个家政公司帮你打扫房间,不用关心王阿姨请假或者有事。 你称之为 “中介者原则”,依赖中介去打扫卫生,而不是依赖某个阿姨,阿姨会有事,中介公司不打样。
实践中我们依赖抽象不依赖实现,如果发现依赖实现,此时重构它。
实践中如果写 “Hello World” ,我不会考虑任何原则或者模式。
现在开始写线路解析程序,遵循 “中介者原则” 定义一个“抽象解析中介” 然后依赖并实现它。 以后不管再来几个手环厂家,我们都可以做扩展,而不用修改已有的解析程序。
3、Liskov Substitution Principle
里氏替换原则描述:子类或者派生类可以替代其基类或父类,用子类实例替代父类实例不产生负面影响。
最直观的例子就是,你不能用黄种人作为有色人种或者白色人种的祖先,或者其他人种的祖先作为黄种人的祖先。
Code Show:
public class YellowRace { private string _color; public YellowRace() { _color = "Yellow"; } public virtual string GetColor() { return _color; } } public class WhiteRace : YellowRace { private string _color; public WhiteRace() { _color = "White"; } public override string GetColor() { return _color; } } static void Main(string[] args) { YellowRace yellowRace = new YellowRace(); WhiteRace whiteRace = new WhiteRace(); Console.WriteLine($"Yellow Race Skin Color Is: {yellowRace.GetColor()}"); // 此处使用子类替换父类,会产生负作用 , }
View Code
上面代码违反 LIP 原则,负面影响:即黄种人,皮肤颜色应是黄色。
可以重构,创建一个 Mankind 类, 白种人、黄种人都继承它。
public abstract class Mankind { protected string _color; public abstract string GetColor(); } public class YellowRace: Mankind { public YellowRace() { _color = "Yellow"; } public override string GetColor() { return _color; } } public class WhiteRace : Mankind { public WhiteRace() { _color = "White"; } public override string GetColor() { return _color; } }
View Code
4、Interface Segregation Principle
接口隔离原则描述:多个客户端特定的接口,好过一个超级通用接口,即客户端不应该实现使用不到的接口,或客户端不应该依赖于不使用的方法。
实践中曾经阅读过这样的代码违反ISP 。 一个接口中有好多好多行为,其中一个行为在实现类中只有一个类真正用到了,其余的方法体中都是 “throw new NotImplementedException();” ,此时应该着手重构它。
题外,使用抽象类还是接口 ? 接口通常作为行为规范,表示能干什么 Can Do;而抽象类表示是什么 Is A 。例如:打印机能打印,则打印机应该实现一个打印接口,同时打印机是一个电器则可以继承电器基类。
5、Dependency Inversion Principle
依赖倒置原则描述:应该依赖抽象,而不是具体实现。
现实中最长用的 ORM 框架,设计的时候遵循此原则,依赖抽象契约而不是某种特定数据库的实现。
回到雇佣王阿姨打扫房间的例子,依赖中介公司,就是依赖抽象,所有就可以让任何阿姨打扫房间。
6、DRY
不要重复你自己原则描述:相同的代码不应该存在两份以上。
实践中相同的代码同时存在多份,此时着手重构它。
三、总结
以上介绍了几种编程原则,遵循这些原则,就可以写出 “Good Code”。
适用于目前所有的面向对象语言,这些原则指导软件开发,并积累了一些实践。有助于软件代码清晰可读、可扩展、高内聚低耦合,有助于代码重构,清除代码异味。SOLID 被典型应用在,测试驱动开发、敏捷开发、自适应软件开发等领域。