自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

寫了這么多年代碼,你真的了解SOLID嗎?

開發(fā) 開發(fā)工具
單獨應(yīng)用SOLID的某一個原則并不能讓收益最大化。應(yīng)該把它作為一個整體來理解和應(yīng)用,從而更好地指導(dǎo)你的軟件設(shè)計。

盡管大家都認(rèn)為SOLID是非常重要的設(shè)計原則,并且對每一條原則都耳熟能詳,但我發(fā)現(xiàn)大部分開發(fā)者并沒有真正理解。要獲得***收益,就必須理解它們之間的關(guān)系,并綜合應(yīng)用所有這些原則。只有把SOLID作為一個整體,才可能構(gòu)建出堅實(Solid)的軟件。遺憾的是,我們看到的書籍和文章都在羅列每個原則,沒有把它們作為一個整體來看,甚至提出SOLID原則的Bob大叔也沒能講透徹。因此我嘗試介紹一下我的理解。

先拋出我的觀點: 單一職責(zé)是所有設(shè)計原則的基礎(chǔ),開閉原則是設(shè)計的***目標(biāo)。里氏替換原則強調(diào)的是子類替換父類后程序運行時的正確性,它用來幫助實現(xiàn)開閉原則。而接口隔離原則用來幫助實現(xiàn)里氏替換原則,同時它也體現(xiàn)了單一職責(zé)。依賴倒置原則是過程式編程與OO編程的分水嶺,同時它也被用來指導(dǎo)接口隔離原則。關(guān)系如下圖:

單一職責(zé)原則(Single Responsibility Principle, SRP)

單一職責(zé)是最容易理解的設(shè)計原則,但也是被違反得最多的設(shè)計原則之一。

要真正理解并正確運用單一職責(zé)原則,并沒有那么容易。單一職責(zé)就跟“鹽少許”一樣,不好把握。Robert C. Martin(又名“Bob大叔”)把職責(zé)定義為變化原因,將單一職責(zé)描述為 ”A class should have only one reason to change." 也就是說,如果有多種變化原因?qū)е乱粋€類要修改,那么這個類就違反了單一職責(zé)原則。那么問題來了,什么是“變化原因”呢?

利益相關(guān)者角色是一個重要的變化原因,不同的角色會有不同的需求,從而產(chǎn)生不同的變化原因。作為居民,家用的電線是普通的220V電線,而對電網(wǎng)建設(shè)者,使用的是高壓電線。用一個Wire類同時服務(wù)于兩類角色,通常意味著壞味道。

變更頻率是另一個值得考慮的變化原因。即使對同一類角色,需求變更的頻率也會存在差異。最典型的例子是業(yè)務(wù)處理的需求比較穩(wěn)定,而業(yè)務(wù)展示的需求更容易發(fā)生變更,畢竟人總是喜新厭舊的。因此這兩類需求通常要在不同的類中實現(xiàn)。

單一職責(zé)原則某種程度上說是在分離關(guān)注點。分離不同角色的關(guān)注點,分離不同時間的關(guān)注點。

在實踐中,怎么運用單一職責(zé)原則呢?什么時候要拆分,什么時候要合并?我們看看新廚師在學(xué)炒菜時,是如何掌握“鹽少許”的。他會不斷地品嘗,直到味道剛好為止。寫代碼也一樣,你需要識別需求變化的信號,不斷“品嘗”你的代碼,當(dāng)“味道”不夠好時,持續(xù)重構(gòu),直到“味道”剛剛好。

開閉原則(Open-closed Principle)

開閉原則指軟件實體(類、模塊等)應(yīng)當(dāng)對擴展開放,對修改閉合。這聽起來似乎很不合理,不能修改,只能擴展?那我怎么寫代碼?

我們先看看為什么要有開閉原則。假設(shè)你是一名成功的開源類庫作者,很多開發(fā)者使用你的類庫。如果某天你要擴展功能,只能通過修改某些代碼完成,結(jié)果導(dǎo)致類庫的使用者都需要修改代碼。更可怕的是,他們被迫修改了代碼后,又可能造成別的依賴者也被迫修改代碼。這種場景絕對是一場災(zāi)難。

如果你的設(shè)計是滿足開閉原則的,那就完全是另一種場景。你可以通過擴展,而不是修改來改變軟件的行為,將對依賴方的影響降到***。

這不正是設(shè)計的***目標(biāo)嗎?解耦、高內(nèi)聚、低耦合等等設(shè)計原則最終不都是為了這個目標(biāo)嗎?暢想一下,類、模塊、服務(wù)都不需要修改,而是通過擴展就能夠改變其行為。就像計算機一樣,組件可以輕松擴展。硬盤太小?直接換個大的,顯示器不夠大的?來個8K的怎么樣?

[[245290]]

什么時候應(yīng)該應(yīng)用開閉原則,怎么做到呢?沒有人能夠在一開始就識別出所有擴展點,也不可能在所有地方都預(yù)留出擴展點,這么做的成本是不可接受的。因此一定是由需求變化驅(qū)動。如果你有領(lǐng)域?qū)<业闹С?,他可以幫你識別出變化點。否則,你應(yīng)該在變化發(fā)生時來做決策,因為在沒有任何依據(jù)時做過多預(yù)先設(shè)計違反了Yagni。

實現(xiàn)開閉原則的關(guān)鍵是抽象。在Bertrand Meyer提出開閉原則的年代(上世紀(jì)80年代),在類庫中增加屬性或方法,都不可避免地要修改依賴此類庫的代碼。這顯然導(dǎo)致軟件很難維護,因此他強調(diào)的是要允許通過繼承來擴展類。隨著技術(shù)發(fā)展,我們有了更多的方法來實現(xiàn)開閉原則,包括接口、抽象類、策略模式等。

我們也許永遠都無法完全做到開閉原則,但不妨礙它是設(shè)計的***目標(biāo)。SOLID的其它原則都直接或間接為開閉原則服務(wù),例如接下來要介紹的里氏替換原則。

里氏替換原則 (The Liskov Substitution Principle)

里氏替換原則說的是派生類(子類)對象能夠替換其基類(父類)對象被使用。學(xué)過OO的同學(xué)都知道,子類本來就可以替換父類,為什么還要里氏替換原則呢?這里強調(diào)的不是編譯錯誤,而是程序運行時的正確性。

程序運行的正確性通??梢苑譃閮深悺R活愂遣荒艹霈F(xiàn)運行時異常,最典型的是UnsupportedOperationException,也就是子類不支持父類的方法。第二類是業(yè)務(wù)的正確性,這取決于業(yè)務(wù)上下文。

下例中,由于java.sql.Date不支持父類的toInstance方法,當(dāng)父類被它替換時,程序無法正常運行,破壞了父類與調(diào)用方的契約,因此違反了里氏替換原則。

  1. package java.sql; 
  2.  
  3. public class Date extends java.util.Date { 
  4.   @Override 
  5.   public Instant toInstant() { 
  6.     throw new java.lang.UnsupportedOperationException(); 
  7.   } 
  8. }   

接下來我們看破壞業(yè)務(wù)正確性的例子,最典型的例子就是Bob大叔在《敏捷軟件開發(fā):原則、模式與實踐》中講到的正方形繼承矩形的例子了。從一般意義來看,正方形是一種矩形,但這種繼承關(guān)系破壞了業(yè)務(wù)的正確性。

  1. public class Rectangle { 
  2.   double width; 
  3.   double height; 
  4.    
  5.   public double area() { 
  6.     return width * height; 
  7.   } 
  8.  
  9. public class Square extends Rectangle { 
  10.   public void setWidth(double width) { 
  11.     this.width = width; 
  12.     this.height = width
  13.   } 
  14.    
  15.   public void setHeight(double height) {  
  16.     this.height = width
  17.     this.width = width; 
  18.    } 
  19.  } 
  20.   
  21.  public void testArea(Rectangle r) {  
  22.    r.setWidth(5); 
  23.    r.setHeight(4);   
  24.    assert(r.area() == 20); //! 如果r是一個正方形,則面積為16 

代碼中testArea方法的參數(shù)如果是正方形,則面積是16,而不是期望的20,所以結(jié)果顯然不正確了。

如果你的設(shè)計滿足里氏替換原則,那么子類(或接口的實現(xiàn)類)就可以保證正確性的前提下替換父類(或接口),改變系統(tǒng)的行為,從而實現(xiàn)擴展。BranchByAbstraction和絞殺者模式都是基于里氏替換原則,實現(xiàn)系統(tǒng)擴展和演進。這也就是對修改封閉,對擴展開放,因此里氏替換原則是實現(xiàn)開閉原則的一種解決方案。

而為了達成里氏替換原則,你需要接口隔離原則。

接口隔離原則 (Interface Segregation Principle)

接口隔離原則說的是客戶端不應(yīng)該被迫依賴于它不使用的方法。簡單來說就是更小和更具體的瘦接口比龐大臃腫的胖接口好。

胖接口的職責(zé)過多,很容易違反單一職責(zé)原則,也會導(dǎo)致實現(xiàn)類不得不拋出UnsupportedOperationException這樣的異常,違反里氏替換原則。因此,應(yīng)該將接口設(shè)計得更瘦。

怎么給接口減肥呢?接口之所以存在,是為了解耦。開發(fā)者常常有一個錯誤的認(rèn)知,以為是實現(xiàn)類需要接口。其實是消費者需要接口,實現(xiàn)類只是提供服務(wù),因此應(yīng)該由消費者(客戶端)來定義接口。理解了這一點,才能正確地站在消費者的角度定義Role interface,而不是從實現(xiàn)類中提取Header Interface。

什么是Role interface? 舉個例子,磚頭(Brick)可以被建筑工人用來蓋房子,也可以被用來正當(dāng)防衛(wèi):

  1. public class Brick { 
  2.   private int length; 
  3.   private int width; 
  4.   private int height; 
  5.   private int weight; 
  6.    
  7.   public void build() { 
  8.     //...包工隊蓋房 
  9.   } 
  10.    
  11.   public void defense() { 
  12.     //...正當(dāng)防衛(wèi) 
  13.   } 

如果直接提取以下接口,這就是Header Interface:

  1. public interface BrickInterface { 
  2.   void buildHouse(); 
  3.   void defense(); 

普通大眾需要的是可以防衛(wèi)的武器,并不需要用磚蓋房子。當(dāng)普通大眾(Person)被迫依賴了自己不需要的接口方法時,就違反接口隔離原則。正確的做法是站在消費者的角度,抽象出Role interface:

  1. public interface BuildHouse { 
  2.   void build(); 
  3. public interface StrickCompetence { 
  4.   void defense(); 
  5.  
  6. public class Brick implement BuildHouse, StrickCompetence { 

有了Role interface,作為消費者的普通大眾和建筑工人就可以分別消費自己的接口:

  1. Worker.java 
  2. brick.build(); 
  3.  
  4. Person.java 
  5. brick.strike(); 

接口隔離原則本質(zhì)上也是單一職責(zé)原則的體現(xiàn),同時它也服務(wù)于里氏替換原則。而接下來介紹的依賴倒置原則可以用來指導(dǎo)接口隔離原則的實現(xiàn)。

依賴倒置原則 (Dependence Inversion Principle)

依賴倒置原則說的是高層模塊不應(yīng)該依賴底層模塊,兩者都應(yīng)該依賴其抽象。

這個原則其實是在指導(dǎo)如何實現(xiàn)接口隔離原則,也就是前文提到的,高層的消費者不應(yīng)該依賴于具體實現(xiàn),應(yīng)該由消費者定義并依賴于Role interface,底層的具體實現(xiàn)也依賴于Role interface,因為它要實現(xiàn)此接口。

依賴倒置原則是區(qū)分過程式編程和面向?qū)ο缶幊痰姆炙畮X。過程式編程的依賴沒有倒置,A Simple DIP Example | Agile Principles, Patterns, and Practices in C#這篇文章以開關(guān)和燈的例子很好地說明了這一點。

上圖的關(guān)系中,當(dāng)Button直接調(diào)用燈的開和關(guān)時,Button就依賴于燈了。其代碼完全是過程式編程:

  1. public class Button {  
  2.   private Lamp lamp;  
  3.   public void Poll()   { 
  4.     if (/*some condition*/) 
  5.       lamp.TurnOn();  
  6.   } 

如果Button還想控制電視機,微波爐怎么辦?應(yīng)對這種變化的辦法就是抽象,抽象出Role interface ButtonServer:

不管是電燈,還是電視機,只要實現(xiàn)了ButtonServer,Button都可以控制。這是面向?qū)ο蟮木幊谭绞健?/p>

總結(jié)

總的來說,單獨應(yīng)用SOLID的某一個原則并不能讓收益***化。應(yīng)該把它作為一個整體來理解和應(yīng)用,從而更好地指導(dǎo)你的軟件設(shè)計。單一職責(zé)是所有設(shè)計原則的基礎(chǔ),開閉原則是設(shè)計的***目標(biāo)。里氏替換原則強調(diào)的是子類替換父類后程序運行時的正確性,它用來幫助實現(xiàn)開閉原則。而接口隔離原則用來幫助實現(xiàn)里氏替換原則,同時它也體現(xiàn)了單一職責(zé)。依賴倒置原則是過程式編程與OO編程的分水嶺,同時它也被用來指導(dǎo)接口隔離原則。

【本文是51CTO專欄作者“ThoughtWorks”的原創(chuàng)稿件,微信公眾號:思特沃克,轉(zhuǎn)載請聯(lián)系原作者】

戳這里,看該作者更多好文

責(zé)任編輯:趙寧寧 來源: 51CTO專欄
相關(guān)推薦

2018-10-07 06:30:40

代碼設(shè)計模式面向?qū)ο笤瓌t

2024-02-20 08:09:51

Java 8DateUtilsDate工具類

2017-11-30 07:30:27

程序員代碼軟件世界觀

2020-07-03 08:11:33

代碼登錄方式

2021-02-03 08:24:32

JavaScript技巧經(jīng)驗

2020-01-06 08:40:42

Windows 10Windows超強模式

2023-11-13 08:49:54

2023-09-28 11:45:09

泛型類對象編譯器

2021-09-11 22:56:58

微信功能技巧

2015-03-27 10:20:41

谷歌地圖谷歌偉大

2021-05-21 05:24:03

Excel數(shù)據(jù)技巧

2020-05-29 14:18:12

Java泛型數(shù)據(jù)

2020-05-22 13:35:39

Java 開發(fā)者代碼

2022-07-26 00:00:22

HTAP系統(tǒng)數(shù)據(jù)庫

2014-04-17 16:42:03

DevOps

2023-07-05 08:05:17

Goerror應(yīng)用場景

2022-12-26 07:43:44

SpringBootWeb 類框架的

2021-11-09 09:48:13

Logging python模塊

2021-01-15 07:44:21

SQL注入攻擊黑客

2019-09-16 08:40:42

點贊
收藏

51CTO技術(shù)棧公眾號