Java的SOLID程式設計原則 - Filippo Buletto

banq發表於2019-02-24

SOLID闡述了五種設計原則,可幫助開發人員輕鬆擴充套件和維護軟體:
S - 單一責任原則
O - 開放原則
L - Liskov替代原理
I - 介面隔離原理
D - 依賴倒置原則

單一責任原則

一個類應該有一個,而且只有一個改變的理由。
一個類應該只有一個責任,這意味著類應該高度凝聚並實現強相關的邏輯。實現功能1和功能2和功能3(依此類推)的類違反了SRP。

SRP示例:

// BAD
public class UserSettingService {
  public void changeEmail(User user) {
    if (checkAccess(user)) {
       //Grant option to change
    }
  }
  public boolean checkAccess(User user) {
    //Verify if the user is valid.
  }
}

// GOOD
public class UserSettingService {
  public void changeEmail(User user) {
    if (securityService.checkAccess(user)) {
       //Grant option to change
    }
  }
}
public class SecurityService {
  public static boolean checkAccess(User user) {
    //check the access.
  }
}


SRP味道
  • 單個類中不止一個上下文分隔的程式碼
  • 測試中的大型setup初始化設定(TDD在檢測SRP違規時非常有用)

SRP好處
  • 負責給定用例的分隔類現在可以在應用程式的其他部分中重用
  • 負責給定用例的分隔類現在可以單獨測試

開/閉原則
您應該能夠擴充套件類行為,而無需對其進行修改。
類應該開啟以進行擴充套件並關閉以進行修改。您應該能夠擴充套件類行為而無需修改其實現:

// BAD
public class Logger {
  String logging;
  public Logger(String logging) {
    this.logging = logging;
  }
  public void log() {
    if ("console".equals(logging)) {
      // Log to console
    } else if ("file".equals(logging)) {
      // Log to file
    }
  }
}

// GOOD
public interface Log {
    void log();
}
public class ConsoleLog implements Log {
  void log() {
    // Log to console
  }
}
public class FileLog implements Log {
  void log() {
    // Log to file
  }
}
public class Logger {
  Log log;
  public Logger(Log log) {
    this.log = log;
  }
  public void log() {
    this.log.log();
  }
}

OCP程式碼味道:
  • 如果你注意到類X直接引用其程式碼庫中的其他類Y,則表明類Y應該傳遞給類X(透過建構函式/單個方法),例如透過依賴注入
  • 複雜的if-else或switch語句

OCP好處:
  • 使用封裝在單獨類中的新功能可以輕鬆擴充套件X類功能,而無需更改類X實現(它不知道引入的更改)
  • 程式碼鬆散耦合
  • 注入的Y類可以在測試中輕易模擬



利斯科夫替代原則
派生類必須可替代其基類。
這是開/閉原則的​​延伸。派生類不應更改基類的行為(繼承方法的行為)。如果類Y是類X的子類,則任何引用類X的例項也應該能夠引用類Y(派生型別必須完全替代它們的基型別)。

// BAD
public class DataHashSet extends HashSet {
  int addCount = 0;
  public boolean function add(Object object) {
    addCount++;
    return super.add(object);
  }
  // the size of count will be added twice!
  public boolean function addAll(Collection collection) {
    addCount += collection.size();
    return super.addAll(collection);
  }
}

// GOOD: Delegation Over Subtyping
public class DataHashSet implements Set {
  int addCount = 0;
  Set set;
  public DataHashSet(Set set) {
    this.set = set;
  }
  public boolean add(Object object) {
    addCount++;
    return this.set.add(object);
  }
  public boolean addAll(Collection collection) {
    addCount += collection.size();
    return this.set.addAll(collection);
  }
}


LSP程式碼味道:
  • 如果它看起來像一隻鴨子,嘎嘎叫像鴨子但需要電池才能達到這個目的 - 這可能違反了LSP
  • 修改子類中的繼承行為
  • 在重寫的繼承方法中引發的異常

LSP好處:
  • 避免意外和不正確的結果
  • 明確區分共享繼承介面和擴充套件功能


介面隔離原理
製作客戶端特定的細粒度介面。
一旦介面變得太大/太胖,我們絕對需要將其拆分為更具體的小介面。介面將由將使用它的客戶端定義,這意味著介面的客戶端將只知道與它們相關的方法。

// BAD
public interface Car {
  Status open();
  Speed drive(Gas gas);
  Engine changeEngine(Engine newEngine);
}
public class Driver {
  public Driver(Car car) {}
  public Speed ride() {
    this.car.open();
    return this.car.drive(new Gas(10));
  }
}
public class Mechanic {
  public Mechanic(Car car) {}
  public Engine fixEngine(Engine newEngine) {
    return this.car.changeEngine(newEngine);
  }
}

// GOOD
public interface RidableCar {
  Status open();
  Speed drive(Gas gas);
}
public interface FixableCar {
    Engine changeEngine(Engine newEngine);
  }
public class Driver {
  // Same with RidableCar
}
public class Mechanic {
  // Same with FixableCar
}

ISP程式碼味道
  • 由許多類實現的一個胖介面,其中沒有一個類實現100%的介面方法。這種胖介面應該分成適合客戶需求的較小介面

ISP好處
  • 高度凝聚力的程式碼
  • 避免使用單個胖介面在所有類之間進行耦合(一旦單個胖介面中的方法得到更新,所有類 - 無論是否使用此方法 - 都被迫相應地更新)
  • 透過將職責分組到單獨的介面中,明確分離業務邏輯


依賴倒置原則
依賴於抽象,而不是實現
如果您的實現細節將取決於更高階別的抽象,它將幫助您獲得正確耦合的系統。而且,它將影響該系統的封裝和內聚。

// BAD
public class SQLDatabase {
  public void connect() {
    String connectionstring = System.getProperty("MSSQLConnection");
    // Make DB Connection
  }
  public Object search(String key) {
    // Do SQL Statement
    return query.find();
  }
}
public class DocumentDatabase {
  // Same logic but with document details
}

// GOOD
public interface Connector {
  Connection open();
}
public interface Finder {
  Object find(String key);
}
public class MySqlConnector implements Connector {}
public class DocumentConnector implements Connector {}
public class MySqlFinder implements Finder {}
public class DocumentFinder implements Finder {}

public class Database {
  public Database(Connector connector,
                  Finder finder) {
    this.connector = connector;
    this.finder = finder;
  }
  public Connection connect() {
    return connector.open();
  }
  public Object search(String key) {
    return finder.find(key);
  }
}


DIP味道:
  • 在高階模組中例項化低階模組
  • 呼叫低階模組/類的類方法


DIP好處:
  • 透過使更高階別的模組獨立於較低階別的模組來提高其可重用性
  • 可以採用依賴性注入1來促進所選擇的低階元件實現的執行時供應到高階元件
  • 注入類可以在測試中輕易模擬



 

相關文章