譯者 | 劉濤
審校 | 重樓
在軟件開發(fā)領(lǐng)域,隨著項(xiàng)目規(guī)模的不斷擴(kuò)大和復(fù)雜度的日益提升,如何確保代碼的組織性、可維護(hù)性和可擴(kuò)展性成為了開發(fā)者們面臨的重大挑戰(zhàn)。為了應(yīng)對(duì)這一挑戰(zhàn),設(shè)計(jì)模式應(yīng)運(yùn)而生并成為發(fā)揮作用的關(guān)鍵所在。它為常見的軟件設(shè)計(jì)難題提供了經(jīng)過實(shí)踐檢驗(yàn)的、可重復(fù)使用的解決方案,從而能夠顯著提升編寫代碼的效率并更易管理。
在本指南當(dāng)中,我們將會(huì)深入剖析一些備受歡迎的設(shè)計(jì)模式,并詳盡闡述如何在Spring Boot框架里有效地實(shí)施這些模式。當(dāng)你完成本指南的學(xué)習(xí)之后,你不但會(huì)對(duì)這些設(shè)計(jì)模式的概念有著深刻的理解,還能夠信心十足地將它們?nèi)谌氲?/span>自己的項(xiàng)目之中。
目錄
- 設(shè)計(jì)模式簡(jiǎn)介
- 如何搭建Spring Boot項(xiàng)目
- 什么是單例模式?
- 什么是工廠模式?
- 什么是策略模式?
- 什么是觀察者模式?
- 如何使用Spring Boot的依賴注入
- 最佳實(shí)踐與優(yōu)化建議
- 結(jié)論與要點(diǎn)總結(jié)
設(shè)計(jì)模式簡(jiǎn)介
設(shè)計(jì)模式是針對(duì)軟件設(shè)計(jì)中常見問題的可復(fù)用解決方案,是從成功項(xiàng)目中提煉出的最佳實(shí)踐模板。雖然設(shè)計(jì)模式具有跨語(yǔ)言的適用性,并不局限于某一種特定的編程語(yǔ)言。但因其基于面向?qū)ο蟮脑O(shè)計(jì)原則,在Java等具有強(qiáng)大面向?qū)ο筇匦缘恼Z(yǔ)言的使用中發(fā)揮的作用尤為強(qiáng)大。
在本指南中,我們將涵蓋如下內(nèi)容:
- 單例模式:確保一個(gè)類僅有一個(gè)實(shí)例。
- 工廠模式:在不指明具體類的情形下創(chuàng)建對(duì)象。
- 策略模式:允許程序在運(yùn)行時(shí)選取算法。
- 觀察者模式:構(gòu)建發(fā)布-訂閱關(guān)系。我們不只會(huì)講解這些模式的工作原理,還會(huì)探討如何在Spring Boot框架下將它們應(yīng)用于實(shí)際應(yīng)用程序當(dāng)中。
如何搭建Spring Boot項(xiàng)目
在我們深入研究這些模式之前,先來搭建一個(gè)Spring Boot項(xiàng)目:
儲(chǔ)備知識(shí)
確定你已經(jīng)掌握:
- Java 11+
- Maven
- Spring Boot CLI (optional)
- Postman or curl (for testing)
項(xiàng)目初始化
你可以使用Spring Initializr快速創(chuàng)建一個(gè)Spring Boot項(xiàng)目:
curl https://start.spring.io/starter.zip \
-d dependencies=web \
-d name=DesignPatternsDemo \
-d javaVersion=11 -o design-patterns-demo.zip
unzip design-patterns-demo.zip
cd design-patterns-demo
什么是單例模式?
單例模式(Singleton pattern)是一種設(shè)計(jì)模式,它確保一個(gè)類只有一個(gè)實(shí)例,并提供一個(gè)全局訪問點(diǎn)來訪問該實(shí)例。該模式通常用于日志記錄、配置管理或數(shù)據(jù)庫(kù)連接等服務(wù)。
如何在Spring Boot中實(shí)現(xiàn)單例模式
Spring Boot中的Bean(Bean 是由Spring容器管理的對(duì)象實(shí)例)默認(rèn)都是單例的,這意味著Spring會(huì)自動(dòng)管理這些Bean的生命周期,以確保只存在一個(gè)實(shí)例。然而,了解單例模式在底層是如何工作的仍然很重要,尤其是當(dāng)你沒有使用Spring管理的Bean或需要對(duì)實(shí)例管理進(jìn)行更多的控制時(shí)。
我們通過手動(dòng)實(shí)現(xiàn)單例模式來演示如何在應(yīng)用程序中控制單個(gè)實(shí)例的創(chuàng)建。
第1步:創(chuàng)建一個(gè)LoggerService類
在這個(gè)示例場(chǎng)景中,我們將使用單例模式創(chuàng)建一個(gè)簡(jiǎn)單的日志服務(wù)。目標(biāo)是確保應(yīng)用程序的所有部分都共享并使用一個(gè)統(tǒng)一的日志記錄實(shí)例。
public class LoggerService {
// The static variable to hold the single instance
private static LoggerService instance;
// Private constructor to prevent instantiation from outside
private LoggerService() {
// This constructor is intentionally empty to prevent other classes from creating instances
}
// Public method to provide access to the single instance
public static synchronized LoggerService getInstance() {
if (instance == null) {
instance = new LoggerService();
}
return instance;
}
// Example logging method
public void log(String message) {
System.out.println("[LOG] " + message);
}
}
- 靜態(tài)變量(實(shí)例):這個(gè)變量持有LoggerService類的唯一實(shí)例。
- 私有構(gòu)造函數(shù):構(gòu)造函數(shù)被標(biāo)記為私有,以防止其他類直接創(chuàng)建新的實(shí)例。
- 同步getInstance()方法:該方法被同步以確保線程安全,即使多個(gè)線程同時(shí)嘗試訪問它,程序也只會(huì)創(chuàng)建一個(gè)實(shí)例。
- 延遲初始化:實(shí)例只有在首次被請(qǐng)求時(shí)才被創(chuàng)建(延遲初始化),這種方式在內(nèi)存使用方面非常高效。
實(shí)際應(yīng)用:單例模式通常被用于需要共享資源的場(chǎng)景,如日志記錄、配置設(shè)置或管理數(shù)據(jù)庫(kù)連接等,在這些情況下,通過控制訪問來確保整個(gè)應(yīng)用程序只使用一個(gè)實(shí)例,以避免資源浪費(fèi)和潛在沖突。
第2步:在Spring Boot控制器中使用單例模式
現(xiàn)在,讓我們看看如何在Spring Boot控制器中使用已經(jīng)創(chuàng)建好的LoggerService單例。這個(gè)控制器會(huì)公開一個(gè)端點(diǎn)(endpoint),每當(dāng)有請(qǐng)求訪問該端點(diǎn)時(shí),就會(huì)通過LoggerService記錄一條消息。
import org.springframework.http.ResponseEntity;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;
@RestControllerpublic class LogController {
@GetMapping("/log") public ResponseEntity<String> logMessage() { // Accessing the Singleton instance of LoggerService LoggerService logger = LoggerService.getInstance(); logger.log("This is a log message!"); return ResponseEntity.ok("Message logged successfully"); }}
- GET 端點(diǎn):我們創(chuàng)建了一個(gè)/log端點(diǎn),當(dāng)對(duì)該端點(diǎn)進(jìn)行訪問時(shí),會(huì)使用LoggerService記錄一條消息。
- 單例使用:我們并未創(chuàng)建LoggerService的新實(shí)例,而是調(diào)用 getInstance()方法,以此確保每次所使用的都是同一個(gè)實(shí)例。
- 響應(yīng):在記錄消息之后,端點(diǎn)會(huì)返回一個(gè)表示成功的響應(yīng)。
第3步:測(cè)試單例模式
現(xiàn)在,讓我們使用Postman(一款功能強(qiáng)大的 API開發(fā)工具,主要用于接口測(cè)試、接口開發(fā)和接口文檔生成等場(chǎng)景)或?yàn)g覽器測(cè)試此端點(diǎn):
GET http://localhost:8080/log
預(yù)期輸出:
- 控制臺(tái)日志:[LOG] This is a log message!
- HTTP響應(yīng):Message logged successfully
你可以多次調(diào)用這個(gè)端點(diǎn),通過觀察日志輸出的連續(xù)性,可以驗(yàn)證每次調(diào)用時(shí)使用的都是同一個(gè)LoggerService實(shí)例。
單例模式在實(shí)際環(huán)境中的應(yīng)用場(chǎng)景
以下實(shí)際環(huán)境中的應(yīng)用程序可能會(huì)使用到單例模式的情況:
- 配置管理:確保應(yīng)用程序始終使用一組統(tǒng)一的配置設(shè)定,尤其是在這些設(shè)定是從文件或者數(shù)據(jù)庫(kù)加載的情況下。這有助于維持應(yīng)用程序在不同環(huán)境中的一致性,避免因配置差異導(dǎo)致的運(yùn)行問題。
- 數(shù)據(jù)庫(kù)連接池:要對(duì)數(shù)量有限的數(shù)據(jù)庫(kù)連接的訪問進(jìn)行管控,確保整個(gè)應(yīng)用程序能夠共享同一個(gè)連接池。
- 緩存:維護(hù)一個(gè)單獨(dú)的緩存實(shí)例,防止數(shù)據(jù)出現(xiàn)不一致的情況。
- 日志服務(wù):如本例所示,使用單一的日志服務(wù)來對(duì)應(yīng)用程序不同模塊的日志輸出進(jìn)行集中式管理。
關(guān)鍵要點(diǎn)
- 單例模式是一種簡(jiǎn)潔的設(shè)計(jì)方案,旨在確保一個(gè)類只有一個(gè)實(shí)例被創(chuàng)建。
- 倘若多個(gè)線程正在訪問單例,那么線程安全性就極為關(guān)鍵,這便是為何在示例中使用synchronized關(guān)鍵字來保障線程安全。
- Spring Boot框架中,Bean默認(rèn)就是單例,然而了解如何手動(dòng)實(shí)現(xiàn)它有助你在特定場(chǎng)景下獲取更多的控制權(quán)。以上就是對(duì)單例模式的實(shí)現(xiàn)和應(yīng)用的介紹。接下來,我們將一起探索工廠模式,看看它如何幫助簡(jiǎn)化對(duì)象的創(chuàng)建過程。
什么是工廠模式?
工廠模式(Factory Pattern)支持在不明確指定具體類的情況下創(chuàng)建對(duì)象。當(dāng)你需要根據(jù)某些輸入來實(shí)例化不同類型的對(duì)象時(shí),這個(gè)模式非常有用。
如何在Spring Boot中實(shí)現(xiàn)工廠模式
當(dāng)你需要根據(jù)一定的標(biāo)準(zhǔn)來創(chuàng)建對(duì)象,同時(shí)又希望將對(duì)象的創(chuàng)建流程與主應(yīng)用程序邏輯相分離時(shí),工廠模式就顯得尤為重要。
為了更直觀地理解這一點(diǎn),本節(jié)中我們?cè)O(shè)想這樣一個(gè)場(chǎng)景:構(gòu)建一個(gè)NotificationFactory,用于發(fā)送電子郵件或短信通知。如果你預(yù)見到未來可能會(huì)增加更多類型的通知方式,比如推送通知或應(yīng)用內(nèi)警告,并且你希望在添加這些新功能時(shí)無需改動(dòng)現(xiàn)有的代碼,那么工廠模式就能派上大用場(chǎng)。
步驟1:創(chuàng)建Notification界面
首先,我們需要定義一個(gè)公共接口,這個(gè)接口能被所有類型的通知(如電子郵件、短信等)實(shí)現(xiàn),旨在確保每一種通知類型都具備一個(gè)統(tǒng)一的send()方法,從而保證在使用上的一致性和互換性。
public interface Notification {
void send(String message);
}
- 目的:Notification接口為發(fā)送通知的行為定義了一個(gè)明確的契約,任何實(shí)現(xiàn)此接口的類都必須為send()方法提供實(shí)現(xiàn)。
- 可擴(kuò)展性:采用接口的設(shè)計(jì)方式,為應(yīng)用程序的未來擴(kuò)展提供了極大的便利。在便捷的擴(kuò)展應(yīng)用程序以包含其他類型的通知的同時(shí),而無需修改現(xiàn)有代碼。
步驟2:實(shí)現(xiàn)EmailNotification和SMSNotification
接下來,讓我們實(shí)現(xiàn)兩個(gè)具體的類,一個(gè)用于發(fā)送電子郵件,另一個(gè)用于發(fā)送短信。
public class EmailNotification implements Notification {
@Override
public void send(String message) {
System.out.println("Sending Email: " + message);
}
}
public class SMSNotification implements Notification {
@Override
public void send(String message) {
System.out.println("Sending SMS: " + message);
}
}
步驟3:創(chuàng)建一個(gè)NotificationFactory
NotificationFactor類負(fù)責(zé)根據(jù)指定的類型創(chuàng)建Notification實(shí)例。這種設(shè)計(jì)的好處在于,它確保NotificationController不需要了解對(duì)象創(chuàng)建的具體細(xì)節(jié)。
public class NotificationFactory {
public static Notification createNotification(String type) {
switch (type.toUpperCase()) {
case "EMAIL":
return new EmailNotification();
case "SMS":
return new SMSNotification();
default:
throw new IllegalArgumentException("Unknown notification type: " + type);
}
}
}
- 工廠方法(createNotification()):工廠方法(Factory Method)會(huì)接收一個(gè)字符串(類型)作為輸入?yún)?shù),并返回相應(yīng)通知類的實(shí)例。
- Switch語(yǔ)句:Switch語(yǔ)句根據(jù)輸入?yún)?shù)選擇適當(dāng)?shù)耐ㄖ愋汀?/span>
- 異常處理:如果客戶端傳入的類型字符串無法被識(shí)別,則工廠方法會(huì)拋出IllegalArgumentException異常。這確保無效類型能夠被系統(tǒng)及時(shí)捕獲和處理。
為什么使用工廠模式?
- 解耦:工廠模式的核心優(yōu)勢(shì)之一在于它實(shí)現(xiàn)了對(duì)象創(chuàng)建與業(yè)務(wù)邏輯的解耦。這使得代碼更加模塊化,從而更易于維護(hù)。
- 可擴(kuò)展性:如果你想要添加一種新的通知類型,只需更新工廠方法即可,而無需更改控制器邏輯。這種設(shè)計(jì)方式使得代碼更加靈活,能夠輕松應(yīng)對(duì)未來可能出現(xiàn)的新需求。
步驟4:在 Spring Boot 控制器中使用工廠模式
現(xiàn)在,我們要?jiǎng)?chuàng)建一個(gè)Spring Boot控制器,以此將之前的所有內(nèi)容整合起來。這個(gè)控制器會(huì)運(yùn)用NotificationFactory,按照用戶的請(qǐng)求來發(fā)送通知。通過這樣的方式,我們能夠把之前關(guān)于通知?jiǎng)?chuàng)建和處理的各個(gè)部分組合成一個(gè)完整的、可運(yùn)行的功能模塊,在Spring Boot框架下實(shí)現(xiàn)根據(jù)用戶需求進(jìn)行通知發(fā)送的功能。
import org.springframework.http.ResponseEntity;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.RestController;
@RestController
public class NotificationController {
@GetMapping("/notify")
public ResponseEntity<String> notify(@RequestParam String type, @RequestParam String message) {
try {
// Create the appropriate Notification object using the factory
Notification notification = NotificationFactory.createNotification(type);
notification.send(message);
return ResponseEntity.ok("Notification sent successfully!");
} catch (IllegalArgumentException e) {
return ResponseEntity.badRequest().body(e.getMessage());
}
}
}
GET 端點(diǎn) (/notify):
- 控制器公開了一個(gè) /notify 端點(diǎn),該端點(diǎn)接受兩個(gè)查詢參數(shù):type(值為 "EMAIL" 或 "SMS")和 message。
- 使用 NotificationFactory 來創(chuàng)建相應(yīng)的通知類型,并發(fā)送消息。
錯(cuò)誤處理:如果提供了無效的通知類型,控制器會(huì)捕獲 IllegalArgumentException 異常,并返回400 Bad Request響應(yīng)。
步驟5:測(cè)試工廠模式
讓我們使用 Postman 或?yàn)g覽器測(cè)試端點(diǎn):
1.發(fā)送電子郵件通知
GET http://localhost:8080/notify?type=email&message=Hello%20Email
輸出:Sending Email: Hello Email
2.發(fā)送SMS通知
GET http://localhost:8080/notify?type=sms&message=Hello%20SMS
輸出:Sending SMS: Hello SMS
3.使用無效類型進(jìn)行測(cè)試
GET http://localhost:8080/notify?type=unknown&message=Test
輸出:Bad Request: Unknown notification type: unknown
工廠模式在實(shí)際環(huán)境中的應(yīng)用場(chǎng)景
Factory 模式在以下情況下特別有用:
- 動(dòng)態(tài)對(duì)象創(chuàng)建:當(dāng)你需要根據(jù)用戶輸入來創(chuàng)建對(duì)象時(shí),例如,在發(fā)送不同類型的通知、生成各種格式的報(bào)告或處理不同的支付方式等場(chǎng)景中,工廠模式可以根據(jù)實(shí)際需求動(dòng)態(tài)地創(chuàng)建相應(yīng)的對(duì)象。
- 對(duì)象創(chuàng)建的解耦:通過使用工廠模式,你可以將主要業(yè)務(wù)邏輯與對(duì)象創(chuàng)建邏輯相分離,從而提高代碼的可維護(hù)性。
- 可擴(kuò)展性:工廠模式還帶來了易于擴(kuò)展的好處。例如,在需要支持新的通知類型時(shí),你只需添加一個(gè)新的實(shí)現(xiàn)Notification接口的類,并升級(jí)工廠模式即可,而無需修改現(xiàn)有的代碼。這使得應(yīng)用程序能夠輕松地適應(yīng)新的需求和變化。
什么是策略模式?
當(dāng)你需要在多個(gè)算法或行為之間實(shí)現(xiàn)動(dòng)態(tài)的切換時(shí),策略模式無疑是一個(gè)極佳的選擇。此模式支持你定義一系列算法,并將每個(gè)算法都封裝在獨(dú)立的類中,從而在運(yùn)行時(shí)能夠輕松地在這些算法之間進(jìn)行切換。這種特性在需要根據(jù)特定條件選擇算法時(shí)尤為有用,它能夠幫助你保持代碼的清晰、模塊化和靈活性。
實(shí)際應(yīng)用案例:設(shè)想一個(gè)電子商務(wù)系統(tǒng),它需要能夠支持多種支付方式,比如信用卡支付、PayPal支付或銀行轉(zhuǎn)賬等。通過巧妙地運(yùn)用策略模式,你可以非常便捷地添加或修改支付方式,而無需對(duì)現(xiàn)有的代碼進(jìn)行任何改動(dòng)。這種設(shè)計(jì)方式能夠確保在引入新功能或?qū)ΜF(xiàn)有功能進(jìn)行更新時(shí),你的應(yīng)用程序依然能夠保持良好的可擴(kuò)展性和可維護(hù)性。
接下來,我們將通過一個(gè)Spring Boot的實(shí)例來具體展示策略模式的應(yīng)用。在這個(gè)實(shí)例中,我們將使用信用卡支付和PayPal支付兩種策略來處理支付事務(wù)。
步驟1:定義PaymentStrategy接口
首先,我們需要定義一個(gè)通用的接口,這個(gè)接口將作為所有支付策略的基礎(chǔ)。這個(gè)接口會(huì)聲明一個(gè)支付方法,具體的支付策略類將會(huì)實(shí)現(xiàn)這個(gè)方法。
public interface PaymentStrategy {
void pay(double amount);
}
PaymentStrategy接口為所有的支付策略定義了一個(gè)統(tǒng)一的規(guī)范,可以確保實(shí)現(xiàn)了這個(gè)接口的支付策略步調(diào)一致。
步驟2:實(shí)現(xiàn)支付策略
為信用卡支付和PayPal支付分別創(chuàng)建具體的實(shí)現(xiàn)類。
public class CreditCardPayment implements PaymentStrategy {
@Override
public void pay(double amount) {
System.out.println("Paid $" + amount + " with Credit Card");
}
}
public class PayPalPayment implements PaymentStrategy {
@Override
public void pay(double amount) {
System.out.println("Paid $" + amount + " via PayPal");
}
}
每個(gè)類都會(huì)實(shí)現(xiàn)PaymentStrategy接口中聲明的pay()方法,并且這個(gè)方法會(huì)包含各自支付方式特有的行為邏輯。
步驟3:在控制器中應(yīng)用策略
創(chuàng)建一個(gè)控制器(controller),用于根據(jù)用戶的輸入來動(dòng)態(tài)地選定支付策略。
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class PaymentController {
@GetMapping("/pay")
public ResponseEntity<String> processPayment(@RequestParam String method, @RequestParam double amount) {
PaymentStrategy strategy = selectPaymentStrategy(method);
if (strategy == null) {
return ResponseEntity.badRequest().body("Invalid payment method");
}
strategy.pay(amount);
return ResponseEntity.ok("Payment processed successfully!");
}
private PaymentStrategy selectPaymentStrategy(String method) {
switch (method.toUpperCase()) {
case "CREDIT": return new CreditCardPayment();
case "PAYPAL": return new PayPalPayment();
default: return null;
}
}
}
服務(wù)端點(diǎn)會(huì)接收支付方法和支付金額作為查詢參數(shù),并根據(jù)這些參數(shù)選擇合適的支付策略來處理支付請(qǐng)求。
步驟4:測(cè)試端點(diǎn)
1.信用卡支付
GET http://localhost:8080/pay?method=credit&amount=100
輸出:Paid $100.0 with Credit Card
2.PayPal支付
GET http://localhost:8080/pay?method=paypal&amount=50
輸出:Paid $50.0 via PayPal
3.無效方法
GET http://localhost:8080/pay?method=bitcoin&amount=25
輸出:Invalid payment method
策略模式的應(yīng)用場(chǎng)景
- 支付處理:在不同支付網(wǎng)關(guān)之間動(dòng)態(tài)切換。
- 排序算法:根據(jù)數(shù)據(jù)規(guī)模選擇最佳的排序方法。
- 文件導(dǎo)出:以多種格式(PDF、Excel、CSV)導(dǎo)出報(bào)告。
關(guān)鍵要點(diǎn)
- 策略模式能夠使代碼保持高度的模塊化,并嚴(yán)格遵循開閉原則,即在不修改現(xiàn)有代碼的基礎(chǔ)上擴(kuò)展新功能。
- 當(dāng)需要添加新的策略時(shí),只需簡(jiǎn)單地創(chuàng)建一個(gè)新的類,該類實(shí)現(xiàn)PaymentStrategy接口即可。這種設(shè)計(jì)方式使得策略模式的擴(kuò)展性極強(qiáng),能夠輕松應(yīng)對(duì)未來可能出現(xiàn)的新需求。
- 策略模式非常適合那些需要在運(yùn)行時(shí)根據(jù)具體情況靈活選擇算法的場(chǎng)景。通過定義一系列算法,并將它們封裝起來,策略模式使得算法可以獨(dú)立于使用它的客戶端而變化,從而提高了代碼的可維護(hù)性和可擴(kuò)展性。
接下來,我們將探索觀察者模式( observer pattern),它最適用的場(chǎng)景是處理事件驅(qū)動(dòng)型架構(gòu)。
什么是觀察者模式?
觀察者模式極為適用于一種場(chǎng)景:當(dāng)一個(gè)對(duì)象(即主題)的狀態(tài)發(fā)生變化時(shí),需要通知多個(gè)其他對(duì)象(即觀察者)。此模式在事件驅(qū)動(dòng)系統(tǒng)中尤為適用,因?yàn)樗軌蛟诓辉斐山M件間緊密耦合的情況下,將更新信息推送至各個(gè)組件。觀察者模式有助于構(gòu)建清晰的系統(tǒng)架構(gòu),特別是當(dāng)系統(tǒng)的不同部分需要獨(dú)立響應(yīng)變化時(shí),其優(yōu)勢(shì)尤為明顯。
在實(shí)際應(yīng)用中,觀察者模式廣泛應(yīng)用于需要發(fā)送通知或警報(bào)的系統(tǒng),例如聊天應(yīng)用程序或股票價(jià)格跟蹤器。這些系統(tǒng)需要實(shí)時(shí)向用戶推送更新信息。通過采用觀察者模式,可以輕松地添加或刪除通知類型,而無需對(duì)核心邏輯進(jìn)行大幅修改。
為了更直觀地展示如何在Spring Boot中實(shí)現(xiàn)觀察者模式,我們將構(gòu)建一個(gè)簡(jiǎn)單的通知系統(tǒng)示例。該系統(tǒng)在用戶注冊(cè)時(shí),會(huì)觸發(fā)電子郵件和短信通知。通過這一示例,我們將展示如何利用觀察者模式,實(shí)現(xiàn)用戶注冊(cè)事件的通知分發(fā),同時(shí)保持系統(tǒng)架構(gòu)的清晰與靈活。
步驟1:創(chuàng)建observer接口
我們首先需要定義一個(gè)通用接口,這個(gè)接口將被所有的觀察者實(shí)現(xiàn),能夠以一種統(tǒng)一的方式接收通知并作出相應(yīng)的反應(yīng)。
public interface Observer {
void update(String event);
}
該接口充當(dāng)了一個(gè)合約的角色,它明確要求所有觀察者對(duì)象都必須實(shí)現(xiàn)一個(gè)名為 update() 的方法。每當(dāng)主題發(fā)生變化時(shí),該方法就會(huì)觸發(fā)。
步驟2:實(shí)現(xiàn)EmailObserver和SMSObserver
接下來,我們創(chuàng)建Observer接口的兩個(gè)具體實(shí)現(xiàn),分別處理電子郵件和短信通知。
EmailObserver類
public class EmailObserver implements Observer {
@Override
public void update(String event) {
System.out.println("Email sent for event: " + event);
}
}
EmailObserver類負(fù)責(zé)在接到事件通知時(shí)發(fā)送電子郵件通知。
SMSObserver類
public class SMSObserver implements Observer {
@Override
public void update(String event) {
System.out.println("SMS sent for event: " + event);
}
}
SMSObserver類負(fù)責(zé)在收到通知時(shí)處理發(fā)送SMS(短消息服務(wù))通知。
步驟3:創(chuàng)建UserService類(主題)
我們將創(chuàng)建一個(gè)UserService類作為觀察者模式中的“主題”,該類在用戶注冊(cè)時(shí)會(huì)主動(dòng)通知所有已注冊(cè)的觀察者(如郵件服務(wù)、短信服務(wù)等),告知它們用戶注冊(cè)的信息。
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
public class UserService {
private List<Observer> observers = new ArrayList<>();
// Method to register observers
public void registerObserver(Observer observer) {
observers.add(observer);
}
// Method to notify all registered observers of an event
public void notifyObservers(String event) {
for (Observer observer : observers) {
observer.update(event);
}
}
// Method to register a new user and notify observers
public void registerUser(String username) {
System.out.println("User registered: " + username);
notifyObservers("User Registration");
}
}
- 觀察者列表:跟蹤所有已注冊(cè)的觀察者。
- registerObserver()方法:將新的觀察者添加到列表中。
- notifyObservers()方法:當(dāng)事件發(fā)生時(shí),通知所有已注冊(cè)的觀察者。
- registerUser()方法:注冊(cè)新用戶,并觸發(fā)對(duì)所有觀察者的通知。
步驟4:在控制器中使用觀察者模式
最后,我們將創(chuàng)建一個(gè)Spring Boot控制器,以公開一個(gè)用于用戶注冊(cè)的端點(diǎn)。該控制器將在UserService中注冊(cè)EmailObserver和SMSObserver。
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api")
public class UserController {
private final UserService userService;
public UserController() {
this.userService = new UserService();
// Register observers
userService.registerObserver(new EmailObserver());
userService.registerObserver(new SMSObserver());
}
@PostMapping("/register")
public ResponseEntity<String> registerUser(@RequestParam String username) {
userService.registerUser(username);
return ResponseEntity.ok("User registered and notifications sent!");
}
}
- 端點(diǎn)(/register):接收username參數(shù)并據(jù)此完成用戶的注冊(cè)流程,同時(shí)觸發(fā)對(duì)所有觀察者的通知。
- 觀察者:EmailObserver和SMSObserver都已注冊(cè)到UserService中,因此每當(dāng)有新用戶注冊(cè)時(shí),它們都會(huì)收到通知。
測(cè)試觀察者模式
現(xiàn)在,讓我們使用Postman或?yàn)g覽器測(cè)試觀察者模式:
POST http://localhost:8080/api/register?username=JohnDoe
控制臺(tái)中的預(yù)期輸出:
User registered: JohnDoe
Email sent for event: User Registration
SMS sent for event: User Registration
系統(tǒng)在注冊(cè)用戶的同時(shí)能夠通知Email和SMS觀察者,這一過程很好地展示了觀察者模式所具備的靈活性。
觀察者模式的實(shí)際應(yīng)用
- 通知系統(tǒng):當(dāng)特定事件發(fā)生時(shí),能夠通過不同渠道(如電子郵件、短信、推送通知)向用戶發(fā)送更新信息。
- 事件驅(qū)動(dòng)架構(gòu):當(dāng)執(zhí)行特定操作(如用戶活動(dòng)或系統(tǒng)警報(bào))時(shí),通知多個(gè)子系統(tǒng)。
- 數(shù)據(jù)流:實(shí)時(shí)向各種消費(fèi)者廣播數(shù)據(jù)變化(例如,實(shí)時(shí)股票價(jià)格或社交媒體動(dòng)態(tài))。
如何使用Spring Boot的依賴注入
到目前為止,我們的示例一直是手動(dòng)創(chuàng)建對(duì)象來演示設(shè)計(jì)模式的工作原理。但在實(shí)際的Spring Boot應(yīng)用程序開發(fā)中,我們更傾向于使用依賴注入(DI:Dependency Injection)這一強(qiáng)大的機(jī)制來管理對(duì)象的創(chuàng)建和裝配。依賴注入不僅讓Spring框架能夠自動(dòng)處理類的實(shí)例化和依賴關(guān)系裝配,還極大地提升了代碼的模塊化程度、可測(cè)試性和可維護(hù)性。
現(xiàn)在,讓我們以策略模式為例,通過重構(gòu)來充分利用Spring Boot的依賴注入能力。這將使我們能夠借助Spring的注解來靈活地管理依賴關(guān)系,并便捷地在不同的支付策略之間進(jìn)行動(dòng)態(tài)切換。
使用Spring Boot的依賴注入更新策略模式
為了重構(gòu)策略模式的示例,我們將采用Spring的@Component、@Service和@Autowired等注解來簡(jiǎn)化依賴注入的流程。以下是具體的步驟:
步驟1:使用@Component注解標(biāo)注支付策略
首先,我們需要在策略的實(shí)現(xiàn)類上使用@Component注解進(jìn)行標(biāo)記。這樣做可以讓Spring能夠自動(dòng)檢測(cè)并管理這些策略類。
@Component("creditCardPayment")public class CreditCardPayment implements PaymentStrategy { @Override public void pay(double amount) { System.out.println("Paid $" + amount + " with Credit Card");
}
}
@Component("payPalPayment")
public class PayPalPayment implements PaymentStrategy {
@Override
public void pay(double amount) {
System.out.println("Paid $" + amount + " using PayPal");
}
}
- @Component注解:通過添加@Component,我們告訴Spring將這些類視為Spring管理的Bean。字符串值(“creditCardPayment"和"payPalPayment”)作為Bean的標(biāo)識(shí)符。
- 靈活性:這種設(shè)置允許我們通過使用適當(dāng)?shù)?/span>Bean標(biāo)識(shí)符在策略之間切換。
步驟2:重構(gòu)PaymentService以使用依賴注入
接下來,讓我們修改PaymentService,以便使用@Autowired和@Qualifier注入特定的支付策略。
@Service
public class PaymentService {
private final PaymentStrategy paymentStrategy;
@Autowired
public PaymentService(@Qualifier("payPalPayment") PaymentStrategy paymentStrategy) {
this.paymentStrategy = paymentStrategy;
}
public void processPayment(double amount) {
paymentStrategy.pay(amount);
}
}
- @Service注解:將PaymentService標(biāo)記為Spring管理的服務(wù)Bean。
- @Autowired:Spring會(huì)自動(dòng)注入所需的依賴。
- @Qualifier:指定要注入的PaymentStrategy的實(shí)現(xiàn)。在本例中,我們使用的是"payPalPayment"。
- 配置的便捷性:只需更改@Qualifier的值,即可在不更改任何業(yè)務(wù)邏輯的情況下切換支付策略。
步驟3:在控制器中使用重構(gòu)后的服務(wù)
為了看到重構(gòu)的好處,讓我們更新控制器以使用我們的PaymentService:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api")
public class PaymentController {
private final PaymentService paymentService;
@Autowired
public PaymentController(PaymentService paymentService) {
this.paymentService = paymentService;
}
@GetMapping("/pay")
public String makePayment(@RequestParam double amount) {
paymentService.processPayment(amount);
return "Payment processed using the current strategy!";
}
}
- @Autowired:控制器會(huì)自動(dòng)接收帶有已注入支付策略的PaymentService。
- GET端點(diǎn)(/pay):當(dāng)訪問此端點(diǎn)時(shí),它會(huì)使用當(dāng)前配置的支付策略(在本例中為PayPal)來處理支付。
使用依賴注入測(cè)試重構(gòu)的策略模式
現(xiàn)在,讓我們使用Postman或?yàn)g覽器測(cè)試新的實(shí)現(xiàn):
GET http://localhost:8080/api/pay?amount=100
預(yù)期輸出:Paid $100.0 using PayPal
如果你將PaymentService中的限定符改為“creditCardPayment”,輸出將相應(yīng)地改變:
Paid $100.0 with Credit Card
使用依賴注入的優(yōu)勢(shì)
- 松耦合:服務(wù)和控制器不需要知道支付處理的具體過程和細(xì)節(jié)。它們僅僅依靠Spring的依賴注入功能來自動(dòng)獲取正確的支付處理實(shí)現(xiàn)。
- 模塊化:你可以便捷地添加新的支付方法(例如,銀行轉(zhuǎn)賬支付、加密貨幣支付),方法是通過創(chuàng)建帶有@Component注解的新類并調(diào)整@Qualifier。
- 可配置性:通過利用Spring配置文件(Spring Profiles是Spring框架中提供的一種機(jī)制,它允許開發(fā)者根據(jù)不同的環(huán)境或配置來管理應(yīng)用程序的不同部分),你可以根據(jù)環(huán)境(例如,開發(fā)環(huán)境與生產(chǎn)環(huán)境)切換策略。
示例:你可以使用@Profile根據(jù)活動(dòng)的配置文件自動(dòng)注入不同的策略:
@Component
@Profile("dev")
public class DevPaymentStrategy implements PaymentStrategy { /* ... */ }
@Component
@Profile("prod")
public class ProdPaymentStrategy implements PaymentStrategy { /* ... */ }
關(guān)鍵要點(diǎn)
- 通過使用Spring Boot的依賴注入(DI),你可以簡(jiǎn)化對(duì)象的創(chuàng)建并提高代碼的靈活性。
- 策略模式與依賴注入相結(jié)合,使你能夠便捷地在不同的策略之間切換,而無需更改核心業(yè)務(wù)邏輯。
通過使用@Qualifier注解以及Spring的配置文件(Profiles),你可以獲得根據(jù)不同環(huán)境或特定需求靈活配置應(yīng)用程序的能力。
這種方法不僅使你的代碼更加清晰,而且還為代碼進(jìn)行高級(jí)配置和擴(kuò)展做好了準(zhǔn)備。在下一節(jié)中,我們將探討最佳實(shí)踐和優(yōu)化技巧,從而將你的Spring Boot應(yīng)用程序提升到新的水平。
通過遵循這些最佳實(shí)踐和優(yōu)化技巧,你可以進(jìn)一步提高Spring Boot應(yīng)用程序的性能、可維護(hù)性和可擴(kuò)展性。這些技巧可能包括優(yōu)化數(shù)據(jù)庫(kù)訪問、使用緩存、減少不必要的依賴注入、以及利用Spring Boot提供的各種內(nèi)置功能來簡(jiǎn)化配置和部署等。記住,持續(xù)學(xué)習(xí)和探索新技術(shù)是成為一名優(yōu)秀開發(fā)者的關(guān)鍵。
最佳實(shí)踐與優(yōu)化建議
最佳實(shí)踐通用原則
- 適度使用設(shè)計(jì)模式:設(shè)計(jì)模式是解決特定問題的有效方案,但不應(yīng)過度使用。過度使用設(shè)計(jì)模式會(huì)導(dǎo)致代碼變得復(fù)雜,增加理解和維護(hù)的難度。
- 優(yōu)先選擇組合而非繼承:在軟件設(shè)計(jì)中,組合通常比繼承更具優(yōu)勢(shì),策略模式和觀察者模式很好地體現(xiàn)了這一原則。
- 保持設(shè)計(jì)模式的靈活性: 利用接口是保持設(shè)計(jì)模式靈活性的重要手段。接口可以將實(shí)現(xiàn)與定義分離,從而使代碼解耦。
性能考慮
- 單例模式:通過使用synchronized 關(guān)鍵字或比爾·普格(Bill Pugh)單例模式來確保線程安全。
- 工廠模式:如果在工廠模式中創(chuàng)建對(duì)象的成本較高,那么對(duì)創(chuàng)建的對(duì)象進(jìn)行緩存是一種提高性能的有效方法。
- 觀察者模式:在觀察者模式中,如果存在大量的觀察者,當(dāng)被觀察對(duì)象狀態(tài)發(fā)生變化時(shí)通知所有觀察者可能會(huì)導(dǎo)致阻塞。為了避免這種情況,可以使用異步處理。
高級(jí)主題
- 在工廠模式中使用反射進(jìn)行動(dòng)態(tài)類加載。
- 利用Spring Profiles根據(jù)環(huán)境切換策略。
- 為你的API端點(diǎn)添加Swagger文檔。
結(jié)論與要點(diǎn)總結(jié)
在本指南中,我們深入探討了幾種極為強(qiáng)大的設(shè)計(jì)模式——單例模式、工廠模式、策略模式和觀察者模式,并展示了它們?cè)赟pring Boot框架中的實(shí)現(xiàn)方法。接下來,讓我們對(duì)每種模式進(jìn)行簡(jiǎn)要總結(jié),并提煉出它們各自最適用的應(yīng)用場(chǎng)景:
單例模式:
- 總結(jié):此模式確保一個(gè)類僅有一個(gè)實(shí)例存在,并提供一個(gè)全局的訪問點(diǎn)。
- 最佳適用場(chǎng)景:用于管理共享資源,例如配置設(shè)置、數(shù)據(jù)庫(kù)連接或日志服務(wù)等。當(dāng)你希望在整個(gè)應(yīng)用程序中嚴(yán)格把控對(duì)某一共享實(shí)例的訪問時(shí),單例模式無疑是理想之選。
工廠模式:
- 總結(jié):該模式提供了一種創(chuàng)建對(duì)象的方式,而無需明確指定要實(shí)例化的具體類。它成功地將對(duì)象創(chuàng)建邏輯與業(yè)務(wù)邏輯解耦。
- 最佳適用場(chǎng)景:在需要根據(jù)輸入條件動(dòng)態(tài)創(chuàng)建不同類型對(duì)象的場(chǎng)景中,如通過電子郵件、短信或推送方式發(fā)送通知。工廠模式能夠顯著提升代碼的模塊化和可擴(kuò)展性。
策略模式:
- 總結(jié):此模式允許你定義一系列算法,并將每個(gè)算法獨(dú)立封裝,使它們能夠互換使用。此模式有助于在運(yùn)行時(shí)靈活選擇算法。
- 最佳適用場(chǎng)景:需要?jiǎng)討B(tài)切換不同行為或算法的場(chǎng)景,例如在電子商務(wù)應(yīng)用中處理多樣化的支付方式。策略模式使代碼更加靈活,并嚴(yán)格遵循開閉原則。
觀察者模式:
- 總結(jié):該模式定義了對(duì)象間的一對(duì)多依賴關(guān)系,當(dāng)一個(gè)對(duì)象的狀態(tài)發(fā)生變化時(shí),其所有依賴者都會(huì)自動(dòng)接收到通知。
- 最佳適用場(chǎng)景:事件驅(qū)動(dòng)系統(tǒng),如通知服務(wù)、聊天應(yīng)用中的實(shí)時(shí)更新或需要響應(yīng)數(shù)據(jù)變化的系統(tǒng)。觀察者模式能夠很好地解耦組件,并顯著提升系統(tǒng)的可擴(kuò)展性。
接下來要做什么?
現(xiàn)在你已經(jīng)掌握了這些基礎(chǔ)的設(shè)計(jì)模式,建議你嘗試將它們?nèi)谌氲?/span>你的現(xiàn)有項(xiàng)目中,親身體驗(yàn)它們?nèi)绾蝺?yōu)化你的代碼結(jié)構(gòu)和提升可擴(kuò)展性。以下是一些建議,供你進(jìn)一步探索:
- 嘗試:嘗試實(shí)現(xiàn)其他設(shè)計(jì)模式,如裝飾器模式(Decorator)、代理模式(Proxy)和構(gòu)建器模式(Builder),以豐富你的設(shè)計(jì)模式工具箱。
- 實(shí)踐:使用這些模式對(duì)現(xiàn)有項(xiàng)目進(jìn)行重構(gòu),從而提升其可維護(hù)性。
- 分享:如果你有任何疑問或想分享你的實(shí)踐經(jīng)驗(yàn),請(qǐng)隨時(shí)與我們聯(lián)系!
我希望本指南能夠助你一臂之力,讓你更加深入地理解如何在Java中高效使用設(shè)計(jì)模式。繼續(xù)探索和實(shí)踐,祝你的編碼之旅充滿樂趣!
譯者介紹
劉濤,51CTO社區(qū)編輯,某大型央企系統(tǒng)上線檢測(cè)管控負(fù)責(zé)人。
標(biāo)題:How to Use Design Patterns in Java with Spring Boot – Explained with Code Examples,作者:Birks Sachdev