2019-07-21 solid原则(面相对象的七大原则)

S.O.L.I.D是面向对象设计和编程(OOD&OOP)中几个重要编码原则(Programming Priciple)的首字母缩写。

| SRP | The Single Responsibility Principle | 单一责任原则 |
| OCP | The Open Closed Principle | 开放封闭原则 |
| LSP | The Liskov Substitution Principle | 里氏替换原则 |
| ISP | The Interface Segregation Principle | 接口分离原则 |
| DIP | The Dependency Inversion Principle | 依赖倒置原则 |

solid是一个框架,用于帮助人们设计出更好的面向对象代码。它由Robert Martin作为五大原则引入。

solid代表的含义:

1、单一职责原则(Single Responsibility Principle,SPR)

在面向对象编程和函数式编程中,都可以使用这种思想,即一个类或者一个函数只干一件事,如果这个类或者函数要干多件事,请将它拆分。一个类拥有的功能越多,维护起来就越困难。

2、开/闭原则(Open / close principle,OCP)

即封装原则。对象应该对扩展开放,对修改关闭。

3、LisKov替换原则(LisKov replacement principle,LSP)

LSP是指任何子类型都应该能轻松的被同一对象树下的其他对象替代,而没有任何影响。如一个模型车对象被一个真实车对象替代

4、接口隔离原则(Interface isolation principle,ISP)

ISP是指拥有多个客户定制接口好于为所有客户定制一个通用接口。该原则用于简化实体键的数据交换,一个好的例子是将垃圾、堆肥即回收利用分开,而不是使用一个包含三个特定垃圾的大型垃圾箱。

5、依赖倒置原则(DIP)

DIP是一个指导我们依赖抽象而非具体的原则。也就是说我们应该构建分层或者对象的继承树。

1. 单一责任原则(SRP)
当需要修改某个类的时候原因有且只有一个。换句话说就是让一个类只做一种类型责任,当这个类需要承当其他类型的责任的时候,就需要分解这个类。 类被修改的几率很大,因此应该专注于单一的功能。如果你把多个功能放在同一个类中,功能之间就形成了关联,改变其中一个功能,有可能中止另一个功能,这时就需要新一轮的测试来避免可能出现的问题,非常耗时耗力。

示例:

新建一个Rectangle类,该类包含两个方法,一个用于把矩形绘制在屏幕上,一个方法用于计算矩形的面积。

Rectangle类违反了SRP原则。Rectangle类具有两个职责,如果其中一个改变,会影响到两个应用程序的变化。

一个好的设计是把两个职责分离出来放在两个不同的类中,这样任何一个变化都不会影响到其他的应用程序。

2. 开放封闭原则****(OCP)
软件实体应该是可扩展,而不可修改的。也就是说,对扩展是开放的,而对修改是封闭的。这个原则是诸多面向对象编程原则中最抽象、最难理解的一个。

(1)通过增加代码来扩展功能,而不是修改已经存在的代码。
(2)若客户模块和服务模块遵循同一个接口来设计,则客户模块可以不关心服务模块的类型,服务模块可以方便扩展服务(代码)。
(3)OCP支持替换的服务,而不用修改客户模块。

示例:

<pre style="margin: 0px; padding: 0px; color: rgb(28, 31, 33); font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(248, 250, 252); text-decoration-style: initial; text-decoration-color: initial;">public boolean sendByEmail(String addr, String title, String content) {

}public boolean sendBySMS(String addr, String content) {

}// 在其它地方调用上述方法发送信息sendByEmail(addr, title, content);
sendBySMS(addr, content);</pre>

如果现在又多了一种发送信息的方式,比如可以通过QQ发送信息,那么不仅需要增加一个方法sendByQQ(),还需要在调用它的地方进行修改,违反了OCP原则,更好的方式是

抽象出一个Send接口,里面有个send()方法,然后让SendByEmail和SendBySMS去实现它既可。这样即使多了一个通过QQ发送的请求,那么只要再添加一个SendByQQ实现类实现Send接口既可。这样就不需要修改已有的接口定义和已实现类,很好的遵循了OCP原则。

3. 里氏替换原则****(LSP)

当一个子类的实例应该能够替换任何其超类的实例时,它们之间才具有is-A关系

客户模块不应关心服务模块的是如何工作的;同样的接口模块之间,可以在不知道服务模块代码的情况下,进行替换。即接口或父类出现的地方,实现接口的类或子类可以代入。

示例:

<pre style="margin: 0px; padding: 0px; color: rgb(28, 31, 33); font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(248, 250, 252); text-decoration-style: initial; text-decoration-color: initial;">public class Rectangle { private double width; private double height; public void setWidth(double value) { this.width = value;
} public double getWidth() { return this.width;
} public void setHeight(double value) { this.width = value;
} public double getHeight() { return this.height;
} public double Area() { return this.widththis.height;
}
}public class Square extends Rectangle { /
由于父类Rectangle在设计时没有考虑将来会被Square继承,所以父类中字段width和height都被设成private,在子类Square中就只能调用父类的属性来set/get,具体省略 */}// 测试void TestRectangle(Rectangle r) {
r.Weight=10;
r.Height=20;
Assert.AreEqual(10,r.Weight);
Assert.AreEqual(200,r.Area);
}// 运行良好Rectangle r = new Rectangle ();
TestRectangle(r);// 现在两个Assert测试都失败了Square s = new Square();
TestRectangle(s);</pre>

  LSP让我们得出一个非常重要的结论:一个模型,如果孤立地看,并不具有真正意义上的有效性,模型的有效性只能通过它的客户程序来表现。例如孤立地看Rectangle和Squre,它们时自相容的、有效的;但从对基类Rectangle做了合理假设的客户程序TestRectangle(Rectangle r)看,这个模型就有问题了。**在考虑一个特定设计是否恰当时,不能完全孤立地来看这个解决方案,必须要根据该设计的使用者所作出的合理假设来审视它。**

  目前也有一些技术可以支持我们将合理假设明确化,例如测试驱动开发(Test-Driven Development,TDD)和基于契约设计(Design by Contract,DBC)。但是有谁知道设计的使用者会作出什么样的合理假设呢?大多数这样的假设都很难预料。如果我们预测所有的假设的话,我们设计的 系统可能也会充满不必要的复杂性。推荐的做法是:只预测那些最明显的违反LSP的情况,而推迟对所有其他假设的预测,直到出现相关的脆弱性的臭味(Bad Smell)时,才去处理它们。我觉得这句话还不够直白,Martin Fowler的《Refactoring》一书中“Refused Bequest”(拒收的遗赠)描 述的更详尽:子类继承父类的methods和data,但子类仅仅只需要父类的部分Methods或data,而不是全部methods和data;当这 种情况出现时,就意味这我们的继承体系出现了问题。例如上面的Rectangle和Square,Square本身长和宽相等,几何学中用边长来表示边, 而Rectangle长和宽之分,直观地看,Square已经Refused了Rectangle的Bequest,让Square继承 Rectangle是一个不合理的设计。

  现在再回到面向对象的基本概念上,子类继承父类表达的是一种IS-A关系,IS-A关系这种用法被认为是面向对象分析(OOA)基本技术之一。但正方形的 的确确是一个长方形啊,难道它们之间不存在IS-A关系?关于这一点,《Java与模式》一书中的解释是:我们设计继承体系时,**子类应该是可替代的父类的**,是可替代关系,而不仅仅是IS-A的关系;而PPP一书中的解释是:从行为方式的角度来看,Square不是Rectangle,对象的行为方式才是软件真正所关注的问题;LSP清楚地指出,**OOD中IS-A关系时就行为方式而言的**,客户程序是可以对行为方式进行合理假设的。其实二者表达的是同一个意思。

**4. **接口分离原则****(ISP)****

不能强迫用户去依赖那些他们不使用的接口。换句话说,使用多个专门的接口比使用单一的总接口总要好。

客户模块不应该依赖大的接口,应该裁减为小的接口给客户模块使用,以减少依赖性。如Java中一个类实现多个接口,不同的接口给不用的客户模块使用,而不是提供给客户模块一个大的接口。

示例:

<pre style="margin: 0px; padding: 0px; color: rgb(28, 31, 33); font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(248, 250, 252); text-decoration-style: initial; text-decoration-color: initial;">public interface Animal {

public void eat();      // 吃

public void sleep();    // 睡

public void crawl();     // 爬

public void run();      // 跑}public class Snake implements Animal {    public void eat() {

}    
public void sleep() {

}  
public void crawl() {

}    public void run(){

}

}public class Rabit implements Animal { public void eat() {

}    
public void sleep() {

}  
public void crawl() {

}    public void run(){

}

}</pre>

上面的例子,Snake并没有run的行为而Rabbit并没有crawl的行为,而这里它们却必须实现这样不必要的方法,更好的方法是crawl()和run()单独作为一个接口,这需要根据实际情况进行调整,反正不要把什么功能都放在一个大的接口里,而这些功能并不是每个继承该接口的类都所必须的。

5. ****依赖注入或倒置原则(DIP)****

1. 高层模块不应该依赖于低层模块,二者都应该依赖于抽象
2. 抽象不应该依赖于细节,细节应该依赖于抽象

这个设计原则的亮点在于任何被DI框架注入的类很容易用mock对象进行测试和维护,因为对象创建代码集中在框架中,客户端代码也不混乱。有很多方式可以实现依赖倒置,比如像AspectJ等的AOP(Aspect Oriented programming)框架使用的字节码技术,或Spring框架使用的代理等。

(1).高层模块不要依赖低层模块;
(2).高层和低层模块都要依赖于抽象;
(3).抽象不要依赖于具体实现;
(4).具体实现要依赖于抽象;
(5).抽象和接口使模块之间的依赖分离。

先让我们从宏观上来看下,举个例子,我们经常会用到宏观的一种体系结构模式--layer模式,通过层的概念分解和架构系统,比如常见得三层架构等。那么依赖关系应该是自上而下,也就是上层模块依赖于下层模块,而下层模块不依赖于上层,

这应该还是比较容易理解的,因为越底层的模块相对就越稳定,改动也相对越少,而越上层跟需求耦合度越高,改动也会越频繁,所以自上而下的依赖关系使上层发生变更时,不会影响到下层,降低变更带来的风险,保证系统的稳定。

上面是立足在整体架构层的基础上的结果,再换个角度,从细节上再分析一下,这里我们暂时只关注UI和Service间的关系,如下面这样的依赖关系会有什么样的问题?

第一,当需要追加提供一种新的Service时,我们不得不对UI层进行改动,增加了额外的工作。

第二,这种改动可能会影响到UI,带来风险。

第三,改动后,UI层和Logic层都必须重新再做Unit testing。

那么具体怎么优化依赖关系才能让模块或层间的耦合更低呢?想想前面讲的OCP原则吧,观点是类似的。

我们可以为Service追加一个抽象层,上层UI不依赖于Service的details,UI和Service同时依赖于这个Service的抽象层。

这样改进后会有什么好处呢?

第一,Service进行扩展时,一般情况下不会影响到UI层,UI不需要改动。

第二,Service进行扩展时,UI层不需要再做Unit testing。

总结:

  1. 一个对象只承担一种责任,所有服务接口只通过它来执行这种任务。

  2. 程序实体,比如类和对象,向扩展行为开放,向修改行为关闭。

  3. 子类应该可以用来替代它所继承的类。

  4. 一个类对另一个类的依赖应该限制在最小化的接口上。

  5. 依赖抽象层(接口),而不是具体类。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342