適用於Java開發人員的SOLID設計原則簡介

看看這篇針對Java開發人員的SOLID設計原則簡介。抽絲剝繭,細說架構那些事——【優銳課】設計模式

當你剛接觸軟件工程時,這些原理和設計模式不容易理解或習慣。咱們都遇到了問題,很難理解SOLID + DP的思想,甚至很難正確實施它們。確實,「爲何要SOLID?」的整個概念,以及如何實施設計模式,這須要時間和大量實踐。架構

我能夠說實話,關於SOLID設計模式以及TDD等其餘領域,從本質上講,它們很難教。很難以正確的方式將全部這些知識和信息傳授給年輕人。ide

讓SOLID 變得容易

在本文中,我將以儘量簡單的術語,經過簡單易懂的示例來教授SOLID的每一個字母。函數

SOLID的「S」

S表明SRP(單一責任原則)。基本思想是應用關注點分離,這意味着你應嘗試將關注點分離到不一樣的類中。一堂課應該專一於單個問題,邏輯或單個領域。當域,規範或邏輯發生變化時,它隻影響一個類。工具

實施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

SRP實施後

咱們要作的是獲取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 }

 

SOLID的「O」

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 }

 

 

SOLID的「L」

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違規問題的方法,可是在此不作解釋,由於它不在本文討論範圍以內。

 

SOLID的「I」

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 }

 

 

SOLID的「D」

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設計原理的簡單概述。

相關文章
相關標籤/搜索