看看這篇針對Java開發人員的SOLID設計原則簡介。抽絲剝繭,細說架構那些事——【優銳課】設計模式
當你剛接觸軟件工程時,這些原理和設計模式不容易理解或習慣。咱們都遇到了問題,很難理解SOLID + DP的思想,甚至很難正確實施它們。確實,「爲何要SOLID?」的整個概念,以及如何實施設計模式,這須要時間和大量實踐。架構
我能夠說實話,關於SOLID設計模式以及TDD等其餘領域,從本質上講,它們很難教。很難以正確的方式將全部這些知識和信息傳授給年輕人。ide
在本文中,我將以儘量簡單的術語,經過簡單易懂的示例來教授SOLID的每一個字母。函數
S表明SRP(單一責任原則)。基本思想是應用關注點分離,這意味着你應嘗試將關注點分離到不一樣的類中。一堂課應該專一於單個問題,邏輯或單個領域。當域,規範或邏輯發生變化時,它隻影響一個類。工具
下面,咱們違反了SRP。VehicleServiceResource
類實現了兩種不一樣的方法,並以兩種角色結束。如咱們所見,該類具備兩個標記其用法的註釋。this
一種是向客戶端公開和提供HTTP終結點服務的角色。spa
第二個是車輛服務的角色,該服務從存儲getVehicles()
中獲取車輛並計算總值calculateTotalValue()
:設計
1 @EndPoint("vehicles") 2 @Service 3 public class VehicleServiceResource { 4 … 5 @GET 6 public List getVehicles(){ 7 } 8 public double calculateTotalValue(){} 9 … 10 }
實現SRP的簡單目標是將VehicleServiceResource
分爲兩個不一樣的類:一個用於端點,另外一個用於服務。code
咱們要作的是獲取VehicleServiceResource
類,並將其分爲兩個不一樣的類。orm
VehicleResource
類僅具備一項和一項工做。爲了向客戶端公開HTTP資源工具並向其提供服務,全部與業務邏輯相關的方法均致使VehicleService
類。
1 @EndPoint("vehicles") 2 public class VehicleResource { 3 @Service 4 private VehicleService service; 5 @GET 6 public List getVehicles() { 7 return this.service.getVehicles(); 8 } 9 ... 10 }
咱們建立了一個名爲 VehicleService
的新類。此類實現全部與車輛有關的邏輯。
1 @Service 2 public class VehicleService { 3 ... 4 public List getVehciles() {} 5 public double calculateTotalValue(){} 6 ... 7 }
O表明OCP(開閉原理)。開閉原則指出:
" ... 軟件實體(例如模塊,類,功能等)應打開以進行擴展,但應關閉以進行修改。"
術語「開放擴展」是指咱們能夠在代碼中擴展幷包括額外的案例/功能,而不會更改或影響咱們現有的實現。
術語「關閉以進行修改」表示在添加了附加功能以後,咱們不該修改現有的實現。
一個簡單的違反OCP的行爲:
1 public class VehicleValueCalculator { 2 // lets assume a simple method to calculate the total value of a vehicle 3 // with extra cost depending the type. 4 public double calculateVehicle(Vehicle v){ 5 double value = 0; 6 if(v instanceof Car){ 7 value = v.getValue() + 2.0; 8 } else if(v instanceof MotorBike) { 9 value = v.getValue() + 0.4; 10 } 11 return value; 12 } 13 }
當咱們要包括一種新型車輛「卡車」時,就會違反OCP。須要對calculateVehicle
方法進行重構和代碼修改。
1 public interface IVehicle { 2 double calculateVehicle(); 3 } 4 public class Car implements IVehicle { 5 @Override 6 public double calculateVehicle() { 7 return this.getValue() + 2.0; 8 } 9 } 10 public class MotorBike implements IVehicle { 11 @Override 12 public double calculateVehicle() { 13 return this.getValue() + 0.4; 14 } 15 }
咱們的新卡車
1 public class Truck implements IVehicle { 2 @Override 3 public double calculateVehicle() { 4 return this.getValue() + 3.4; 5 } 6 }
這樣,經過使用一種接受IVehicle的方法,之後在每次添加新型車輛時都無需進行重構/代碼修改。
範例程式碼
1 public class Main { 2 public static void main(String[] args){ 3 IVehicle car = new Car(); 4 IVhecile motorBike = new MotorBike(); 5 //new addition 6 IVhecile truck = new Truck(); 7 double carValue = getVehicleValue(car); 8 double motorBikeValue = getVehicleValue(motorBike); 9 double truckValue = getVehicleValue(truck); 10 } 11 public double getVehicleValue(IVehicle v) { 12 return v.calculateVehicle(); 13 } 14 }
L表明LSP(Liskov替代原理):
爲了使這篇文章成爲SOLID的介紹,而不會引發混淆,我將嘗試使LSP儘量簡單,並排除不少具體的細節,由於LSP又是另外一天的討論和辯論。
LSP指出,當咱們用任何子類型替換父類型時,該軟件不該改變指望的結果。
LSP不只僅是一個設計模式,更是一個問題定義,而咱們能夠作的是防止不良影響。
爲了更清楚地說明這一點,咱們將檢查如下簡單示例:
1 /** 2 * The Base Rectangle class 3 * This class defines the structure and properties of all types of rectangles 4 */ 5 public class Rectangle { 6 private int width; 7 private int height; 8 public Rectangle(){} 9 public Rectangle(int w,int h) { 10 this.width = w; 11 this.height = h; 12 } 13 public int getWidth() { 14 return width; 15 } 16 public void setWidth(int width) { 17 this.width = width; 18 } 19 public int getHeight() { 20 return height; 21 } 22 public void setHeight(int height) { 23 this.height = height; 24 } 25 public int getArea() { 26 return this.height * this.width; 27 } 28 /** 29 * LSP violation is case of a Square reference. 30 */ 31 public final static void setDimensions(Rectangle r,int w,int h) { 32 r.setWidth(w); 33 r.setHeight(h); 34 //assert r.getArea() == w * h 35 } 36 }
1 /** 2 * A Special kind of Rectangle 3 */ 4 public class Square extends Rectangle { 5 @Override 6 public void setHeight(int h){ 7 super.setHeight(h); 8 super.setWidth(h); 9 } 10 @Override 11 public void setWidth(int w) { 12 super.setWidth(w); 13 super.setHeight(w); 14 } 15 }
在談論LSP時,咱們在Rectangle類中有setDimensions方法,該方法接受Rectangle對象的類型並設置寬度和高度。這是違規的,由於行爲發生了變化,而且在傳遞方形引用時咱們的數據不一致。
有不少解決方案。其中一些將應用「開放式封閉原則」和經過「合同」模式進行設計。
還有許多其餘解決LSP違規問題的方法,可是在此不作解釋,由於它不在本文討論範圍以內。
I表明ISP(接口隔離原理)。接口隔離原則是由Robert C. Martin在爲Xerox諮詢時定義的。他將其定義爲:
「不該強迫客戶依賴他們不使用的接口。」
ISP指出,咱們應該將接口拆分爲更小,更具體的接口。
如下是表明兩個不一樣角色的界面示例。一個角色是處理打開和關閉之類的鏈接,另外一個角色是發送和接收數據。
1 public interface Connection { 2 void open(); 3 void close(); 4 byte[] receive(); 5 void send(byte[] data); 6 }
在應用ISP以後,咱們獲得了兩個不一樣的接口,每一個接口表明一個確切的角色。
1 public interface Channel { 2 byte[] receive(); 3 void send(byte[] data); 4 } 5 public interface Connection { 6 void open(); 7 void close(); 8 }
D表明DIP(依賴性反轉原理)。DIP聲明咱們應該依賴抽象(接口和抽象類),而不是具體的實現(類)。
接下來是違反DIP。咱們有一個Emailer
類,具體取決於直接的SpellChecker
類:
1 public class Emailer{ 2 private SpellChecker spellChecker; 3 public Emailer(SpellChecker sc) { 4 this.spellChecker = sc; 5 } 6 public void checkEmail() { 7 this.spellChecker.check(); 8 } 9 }
Spellchecker
類:
1 public class SpellChecker { 2 public void check() throws SpellFormatException { 3 } 4 }
目前可能可使用,可是過了一下子,咱們要包含兩種不一樣的SpellChecker
實現。咱們有默認的SpellChecker
和新greek spellchecker
。
在當前的實現中,須要重構,由於Emailer類僅使用SpellChecker
類。
一個簡單的解決方案是爲不一樣的SpellChecker
建立要實現的接口。
1 // The interface to be implemented by any new spell checker. 2 public interface ISpellChecker { 3 void check() throws SpellFormatException; 4 }
如今,Emailer
類在構造函數上僅接受ISpellChecker
引用。下面,咱們將Emailer
類更改成不關心/不依賴於實現(具體的類),而是依賴於接口(ISpellChecker
)
1 public class Emailer{ 2 private ISpellChecker spellChecker; 3 public Emailer(ISpellChecker sc) { 4 this.spellChecker = sc; 5 } 6 public void checkEmail() { 7 this.spellChecker.check(); 8 } 9 }
咱們爲ISpellChecker
提供了許多實現:
1 public class SpellChecker implements ISpellChecker { 2 @Override 3 public void check() throws SpellFormatException { 4 } 5 } 6 public class GreekSpellChecker implements ISpellChecker { 7 @Override 8 public void check() throws SpellFormatException { 9 } 10 }
這是另外一個代碼示例。不管實現是什麼,咱們都將ISpellChecker
類型傳遞給Emailer構造函數。
1 public static class Main{ 2 public static void main(String[] a) { 3 ISpellChecker defaultChecker = new SpellChecker(); 4 ISpellChecker greekChecker = new GreekSpellChecker(); 5 new Emailer(defaultChecker).checkEmail(); 6 new Emailer(greekChecker).checkEmail(); 7 } 8 }
就是這樣!但願你喜歡Java代碼中SOLID設計原理的簡單概述。