SOLID 原則是一組指導原則,可協助軟體開發人員設計健壯、可擴充且可維護的系統。這些原則由 Robert C. Martin(Bob 叔叔)提出,對於物件導向程式設計建立靈活且可重複使用的程式碼至關重要。
在這篇文章中,我們將深入研究每個 SOLID 原則,解釋其目的,並提供 Java 範例來演示其應用程式。
定義:一個類別應該只有一個改變的理由。這意味著一個類別應該只有一項工作或職責。
當一個類別具有多個職責時,對一項職責的變更可能會影響或破壞程式碼的其他部分。透過遵守 SRP,我們確保了更好的可維護性和可測試性。
// Violating SRP: A class that handles both user authentication and database operations.
class UserManager {
public void authenticateUser(String username, String password) {
// Authentication logic
}
public void saveUserToDatabase(User user) {
// Database logic
}
}
// Following SRP: Separate responsibilities into distinct classes.
class AuthService {
public void authenticateUser(String username, String password) {
// Authentication logic
}
}
class UserRepository {
public void saveUserToDatabase(User user) {
// Database logic
}
}
在此範例中,AuthService 處理身份驗證,UserRepository 管理資料庫操作。每個類別都有一個單一的職責,使程式碼更乾淨、更模組化。
定義:類別應該對擴充開放,但對修改關閉。這意味著您應該能夠在不更改現有程式碼的情況下新增功能。
當您修改現有程式碼時,您可能會面臨引入錯誤的風險。 OCP 透過繼承或組合來促進功能擴展,而不是改變原始實作。
// Violating OCP: Adding a new discount type requires modifying the existing code.
class DiscountCalculator {
public double calculateDiscount(String discountType, double amount) {
if ("NEWYEAR".equals(discountType)) {
return amount * 0.10;
} else if ("BLACKFRIDAY".equals(discountType)) {
return amount * 0.20;
}
return 0;
}
}
// Following OCP: Use polymorphism to add new discount types without changing existing code.
interface Discount {
double apply(double amount);
}
class NewYearDiscount implements Discount {
public double apply(double amount) {
return amount * 0.10;
}
}
class BlackFridayDiscount implements Discount {
public double apply(double amount) {
return amount * 0.20;
}
}
class DiscountCalculator {
public double calculateDiscount(Discount discount, double amount) {
return discount.apply(amount);
}
}
現在,新增的折扣類型只需建立一個實作 Discount 介面的新類別。
定義:子類型必須可以替換其基底類型,而不改變程式的正確性。
使用多態性時,違反 LSP 可能會導致意外的行為和錯誤。派生類別必須遵守其基底類別定義的契約。
// Violating LSP: A subclass changes the behavior of the parent class in an unexpected way.
class Bird {
public void fly() {
System.out.println("Flying...");
}
}
class Penguin extends Bird {
@Override
public void fly() {
throw new UnsupportedOperationException("Penguins can't fly!");
}
}
// Following LSP: Refactor the hierarchy to honor substitutability.
abstract class Bird {
public abstract void move();
}
class FlyingBird extends Bird {
public void move() {
System.out.println("Flying...");
}
}
class Penguin extends Bird {
public void move() {
System.out.println("Swimming...");
}
}
透過重新設計層次結構,FlyingBird 和 Penguin 在替換 Bird 時都能正確運作。
定義:不應強迫客戶端實作他們不使用的介面。相反,建立更小、更具體的介面。
大型介面迫使實作類別包含它們不需要的方法。這會導致程式碼臃腫和不必要的依賴關係。
// Violating ISP: A large interface with unrelated methods.
interface Worker {
void work();
void eat();
}
class Robot implements Worker {
public void work() {
System.out.println("Working...");
}
public void eat() {
// Robots don't eat, but they're forced to implement this method.
throw new UnsupportedOperationException("Robots don't eat!");
}
}
// Following ISP: Split the interface into smaller, focused interfaces.
interface Workable {
void work();
}
interface Eatable {
void eat();
}
class Robot implements Workable {
public void work() {
System.out.println("Working...");
}
}
class Human implements Workable, Eatable {
public void work() {
System.out.println("Working...");
}
public void eat() {
System.out.println("Eating...");
}
}
現在,Robot只實作了Workable接口,避免了不必要的方法。
定義:高層模組不應該依賴低層模組。兩者都應該依賴抽象。
對具體實現的直接依賴使程式碼變得僵化且難以測試。 DIP 提倡使用抽象(介面)來解耦元件。
// Violating DIP: High-level class depends on a low-level implementation.
class MySQLDatabase {
public void connect() {
System.out.println("Connecting to MySQL...");
}
}
class UserService {
private MySQLDatabase database;
public UserService() {
this.database = new MySQLDatabase(); // Tight coupling
}
public void performDatabaseOperation() {
database.connect();
}
}
// Following DIP: High-level class depends on an abstraction.
interface Database {
void connect();
}
class MySQLDatabase implements Database {
public void connect() {
System.out.println("Connecting to MySQL...");
}
}
class UserService {
private Database database;
public UserService(Database database) {
this.database = database; // Depend on abstraction
}
public void performDatabaseOperation() {
database.connect();
}
}
// Usage
Database db = new MySQLDatabase();
UserService userService = new UserService(db);
userService.performDatabaseOperation();
透過這種設計,您可以輕鬆地交換資料庫實作(例如,PostgreSQL、MongoDB),而無需修改 UserService 類別。
SOLID 原則是建立可維護、可擴展且健壯的軟體的強大工具。快速回顧一下:
SRP:一類,一責。
OCP:在不修改現有程式碼的情況下擴充功能。
LSP:子類型必須可替換其基本類型。
ISP:偏好較小的、集中的介面。
DIP:取決於抽象,而不是具體實作。
透過應用這些原則,您的程式碼將更容易理解、測試並適應不斷變化的需求。從小處開始,根據需要進行重構,並逐漸將這些原則融入您的開發過程中!
原文出處:https://dev.to/be11amer/understanding-solid-principles-in-software-design-2b3