【譯】什麼是SOLID原則(第3部分)

翻譯自:What’s the deal with the SOLID principles? (part 3)服務器

讓咱們從最後一個 SOLID 原則開始吧,即依賴倒置原則(Dependency Inversion Principle,簡稱 DIP)(不要和依賴注入Dependency Injection ,DI 弄混淆了)。這個原則所說的是高級模塊不該該依賴具象的低級模塊,它們都應該依賴相應模塊的抽象層。架構

我仍將使用自行車的示例來嘗試給你解釋這個原則。首選看下這個 Bike 接口:app

interface Bike {
  void pedal()
  void backPedal()
}
複製代碼

MountainBikeClassicBike 這兩個類實現了上面的接口:框架

// 山地車
class MountainBike implements Bike {
  override void pedal() {
    // complex code that computes the inner workings of what happens 
    // when pedalling on a mountain bike, which includes taking into 
    // account the gear in which the bike currently is.
  }
  
  override void backPedal() {
    // complex code that computes what happens when we back pedal    
    // on a mountain bike, which is that you pedal in the wrong   
    // direction with no discernible effect on the bike
  }
}

// 傳統自行車
class ClassicBike implements Bike {
  override void pedal() {
    // the same as for the mountain bike with the distinction that 
    // there is a single gear on a classic bike
  }
  
  override void backPedal() {
    // complex code that actually triggers the brake function on the     
    // bike
  }
}
複製代碼

正如你所看到的,踩腳踏板(pedal)會讓自行車向前行駛,可是山地車 MountainBike 由於有多個齒輪,因此它的 pedal 會更加複雜。另外,向後踩腳踏板(back pedal)時,山地車不會作任何事,而傳統自行車 ClassicBike 則會觸發剎車操做。ide

我之因此在每一個方法的註釋中都有提到「complex code」,是由於我想指出咱們應該把上述代碼移動到不一樣的模塊中。咱們這樣作是爲了簡化自行車類以及遵循單一職責原則(自行車類不該該擔起在你向前或向後踩腳踏板時究竟發生了什麼的計算工做,它們應該處理有關自行車的更高級別的事情)。工具

爲了作到這一點,咱們將爲每種類型的 pedalling 建立一些行爲類。post

class MountainBikePedalBehaviour {
  void pedal() {
    //complex code
  }
}

class MountainBikeBackPedalBehaviour {
  void backPedal() {
    // complex code
  }
}

class ClassicBikePedalBehaviour {
  void pedal() {
    // complex code
  }
}

class ClassicBikeBackPedalBehaviour {
  void backPedal() {
    // complex code
  }
}
複製代碼

而後像下面這樣使用這些類:測試

// 山地車
class MountainBike implements Bike {
  override void pedal() {
    var pedalBehaviour = new MountainBikePedalBehaviour()
    pedalBehaviour.pedal()
  }
  
  override void backPedal() {
    var backPedalBehaviour = new MountainBikeBackPedalBehaviour()
    backPedalBehaviour.backPedal()
  }
}

// 傳統自行車
class ClassicBike implements Bike {
  override void pedal() {
    var pedalBehaviour = new ClassicBikePedalBehaviour()
    pedalBehaviour.pedal()
  }
  
  override void backPedal() {
    var backPedalBehaviour = new ClassicBikeBackPedalBehaviour()
    backPedalBehaviour.backPedal()
  }
}
複製代碼

這個時候,咱們能夠很清楚地看到高級模塊 MountainBike 依賴於某些具體的低級模塊 MountainBikePedalBehaviourMountainBikeBackPedalBehaviourClassicBike 以及它的低級模塊一樣如此。根據依賴倒置原則,高級模塊和低級模塊都應該依賴抽象。爲此,咱們須要如下接口:this

interface PedalBehaviour {
  void pedal()
}

interface BackPedalBehaviour {
  void backPedal()
}
複製代碼

除了須要實現上面的接口外,行爲類的代碼與以前無異:url

class MountainBikePedalBehaviour implements PedalBehaviour {
  override void pedal() {
    // same as before
  }
}
複製代碼

剩下的其餘行爲類同上。

如今咱們須要一種方法將 PedalBehaviourBackPedalBehaviour 傳遞給 MountainBikeClassicBike 類。咱們能夠選擇在構造方法、pedal()pedalBack() 中完成這件事。本例中,咱們使用構造方法。

class MountainBike implements Bike {
  
  PedalBehaviour pedalBehaviour;
  BackPedalBehaviour backPedalBehaviour;
  
  public MountainBike(PedalBehaviour pedalBehaviour,
                      BackPedalBehaviour backPedalBehaviour) {
    this.pedalBehaviour = pedalBehaviour;
    this.backPedalBehaviour = backPedalBehaviour;
  }
  
  override void pedal() {
    pedalBehaviour.pedal();
  }
  
  override void backPedal() {
    backPedalBehaviour.backPedal();
  }
}
複製代碼

ClassicBike 類同上。

咱們的高級模塊(MountainBikeClassicBike)再也不依賴於具體的低級模塊,而是依賴於抽象的 PedalBehaviourBackPedalBehaviour

在咱們的例子中,咱們應用的主模塊可能看起來向下面這樣:

class MainModule {
  MountainBike mountainBike;
  ClassicBike classicBike;
  MountainBikePedalBehaviour mountainBikePedalBehaviour;
  ClassicBikePedalBehaviour classicBikePedalBehaviour;
  MountainBikeBackPedalBehaviour mountainBikeBackPedalBehaviour;
  ClassicBikeBackPedalBehaviour classicBikeBackPedalBehaviour;
  
  public MainModule() {
    mountainBikePedalBehaviour = new MountainBikePedalBehaviour();
    mountainBikeBackPedalBehaviour = 
      new MountainBikeBackPedalBehaviour();
    mountainBike = new MountainBike(mountainBikePedalBehaviour,   
                     mountainBikeBackPedalBehaviour);
    
    classicBikePedalBehaviour = new ClassicBikePedalBehaviour();
    classicBikeBackPedalBehaviour = 
      new ClassicBikeBackPedalBehaviour();
    classicBike = new ClassicBike(classicBikePedalBehaviour,
                    classicBikeBackPedalBehaviour);
  }
  
  public void pedalBikes() {
    mountainBike.pedal()
    classicBike.pedal()
  }
  
  public void backPedalBikes() {
    mountainBike.backPedal();
    classicBike.backPedal();
  }
}
複製代碼

能夠看到,咱們的 MainModule 依賴了具體的低級模塊而不是抽象層。咱們能夠經過向構造方法中傳遞依賴來改善這種狀況:

public MainModule(Bike mountainBike, Bike classicBike, PedalBehaviour mBikePB, BackPedalBehaviour mBikeBPB, PedalBehaviour cBikePB, BackPedalBehaviour cBikeBPB)...
複製代碼

如今,MainModule 部分依賴了抽象層,部分依賴了低級模塊,這些低級模塊也依賴了那些抽象層。全部這些模塊之間的關係再也不依賴於實現細節。

在咱們到達應用程序中的最高模塊以前,爲了儘量地延遲一個具體類的實例化,咱們一般要靠依賴注入和實現了依賴注入的框架。你能夠在 這裏 找到更多有關依賴注入的信息。咱們能夠將依賴注入視爲幫助咱們實現依賴倒置的工具。咱們不斷地向依賴鏈中傳遞依賴關係以免具體類的實例化。

那麼爲何要經歷這一切呢?不依賴於具象的一個優勢就是咱們能夠模擬一個類,從而使測試更容易進行。咱們來看一個簡單的例子。

interface Network {
  public String getServerResponse(URL serverURL);
}

class NetworkRequestHandler implements Network {
  override public String getServerResponse(URL serverURL) {
    // network code implementation
  }
}
複製代碼

假設咱們還有一個 NetworkManager 類,它有一個公共方法,經過使用一個 Network 的實例返回服務器響應:

public String getResponse(Network networkRequestHandler, URL url) {
  return networkRequestHandler.getServerResponse(url)
}
複製代碼

由於這樣的代碼結構,咱們能夠測試代碼如何處理來自服務器的「404」響應。爲此,咱們將創 NetworkRequestHandler的模擬版本。咱們之因此能夠這麼作,是由於 NetworkManager 依賴於抽象層,即 Network,而不是某個具體的 NetworkRequestHandler

class Mock404 implements Network {
  override public String getServerResponse(URL serverURL) {
    return "404"
  }
}
複製代碼

經過調用 getResponse 方法,傳遞 Mock404 類的實例,咱們能夠很容易地測試咱們指望的行爲。像 Mockito 這樣的模擬庫能夠幫助你模擬某些類,而無需編寫單獨的類來執行此操做。

除了易於測試,咱們的應用在多變情景下也能應對自如。由於模塊之間的關係是基於抽象的,咱們能夠更改具體模塊的實現,而無需大範圍地更改代碼。

最後一樣重要的是這會讓事情變得更簡單。若是你有留意自行車的示例,你會發現 MountainBikeClassicBike 類很是類似。這就意味着咱們再也不須要單獨的類了。咱們能夠建立一個簡單的實現了 Bike 接口的類 GenericBike,而後山地車和傳統自行車的實例化就像下面這樣:

GenericBike mountainBike = new GenericBike(mbPedalB, mbBackPedalB);
GenericBike classicBike = new GenericBike(cbPedalB, cbBackPedalB);
複製代碼

咱們減小了一半數量的具體自行車類的實現,這意味着咱們的代碼更容易管理。

總結

全部這些原則可能看起來有點矯枉過正,你可能會排斥它們。在很長的一段時間裏,我和你同樣。隨着時間的推移,我開始逐漸把個人代碼向加強可測試性和更易於維護的方向轉變。漸漸地,我開始這樣來思考事情:「若是隻有一種方法能夠把兩個部分的內容分開,並將其放在不一樣的類中,以便我能……」。一般,答案是的確存在這樣的一種方法,而且別人已經實現過了。大多數時候,這種方法都受到 SOLID 原則的啓發。固然,緊迫的工期和其餘現實生活中的因素可能會不容許你遵照全部這些原則。雖然很難 100% 實現 SOLID 原則,可是有比沒有強吧。也許你能夠嘗試只在那些當需求變動時最容易受影響的部分遵照這些原則。你沒必要過度遵循它們,能夠把這些原則視爲你提升代碼質量的指南。若是你不得不須要製做一個快速原型或者驗證一個概念應用的可行性,那麼你沒有必要盡力去搭一個最佳架構。SOLID更像是一個長期策略,對於必須經得起時間考驗的軟件很是有用。

在這篇由三部分組成的文章中,我試圖給你展現了一些有關 SOLID的我發現比較有趣的東西。關於 SOLID,還有不少見解和解釋,爲了更好地理解和從多個角度獲取知識,請多閱讀些其餘文章。

我但願這篇文章對你有所幫助。

……

若是你尚未看過另兩個部分,這裏是它們的連接,第1部分第2部分 。若是你喜歡這篇文章,你能夠在咱們的 官方站點 上找到更多信息。

相關文章
相關標籤/搜索