Spring中如何使用設計模式
關於設計模式,如果使用得當,將會使我們的程式碼更加簡潔,並且更具擴充套件性。本文主要講解Spring中如何使用策略模式,工廠方法模式以及Builder模式。
1. 策略模式
關於策略模式的使用方式,在Spring中其實比較簡單,從本質上講,策略模式就是一個介面下有多個實現類,而每種實現類會處理某一種情況。我們以發獎勵為例進行講解,比如我們在抽獎系統中,有多種獎勵方式可供選擇,比如積分,虛擬幣和現金等。在儲存時,我們必然會使用一個類似於type的欄位用於表徵這幾種發放獎勵的,那麼這裡我們就可以使用多型的方式進行獎勵的發放。比如我們抽象出一個PrizeSender
的介面,其宣告如下:
public interface PrizeSender {
/**
* 用於判斷當前例項是否支援當前獎勵的發放
*/
boolean support(SendPrizeRequest request);
/**
* 發放獎勵
*/
void sendPrize(SendPrizeRequest request);
}
該介面中主要有兩個方法:support()和sendPrize(),其中support()方法主要用於判斷各個子類是否支援當前型別資料的處理,而sendPrize()則主要是用於進行具體的業務處理的,比如這裡獎勵的發放。下面就是我們三種不同型別的獎勵發放的具體程式碼:
// 積分發放 @Component public class PointSender implements PrizeSender { @Override public boolean support(SendPrizeRequest request) { return request.getPrizeType() == PrizeTypeEnum.POINT; } @Override public void sendPrize(SendPrizeRequest request) { System.out.println("發放積分"); } }
// 虛擬幣發放
@Component
public class VirtualCurrencySender implements PrizeSender {
@Override
public boolean support(SendPrizeRequest request) {
return PrizeTypeEnum.VIRTUAL_CURRENCY == request.getPrizeType();
}
@Override
public void sendPrize(SendPrizeRequest request) {
System.out.println("發放虛擬幣");
}
}
// 現金髮放
@Component
public class CashSender implements PrizeSender {
@Override
public boolean support(SendPrizeRequest request) {
return PrizeTypeEnum.CASH == request.getPrizeType();
}
@Override
public void sendPrize(SendPrizeRequest request) {
System.out.println("發放現金");
}
}
這裡可以看到,在每種子型別中,我們只需要在support()方法中通過request的某個引數來控制當前request是否是當前例項能夠處理的型別,如果是,則外層的控制邏輯就會將request交給當前例項進行處理。關於這個類的設計,有幾個點需要注意:
- 使用
@Component
註解對當前類進行標註,將其宣告為Spring容器所管理的一個bean; - 宣告一個返回boolean值的類似於
support()
的方法,通過這個方法來控制當前例項是否為處理目標request的例項; - 宣告一個類似於
sendPrize()
的方法用於處理業務邏輯,當然根據各個業務的不同宣告的方法名肯定是不同的,這裡只是一個對統一的業務處理的抽象; - 無論是
support()
方法還是sendPrize()
方法,都需要傳一個物件進行,而不是簡簡單單的基本型別的變數,這樣做的好處是後續如果要在Request中新增欄位,那麼就不需要修改介面的定義和已經實現的各個子類的邏輯;
2. 工廠方法模式
上面我們講解了如何使用Spring來宣告一個策略模式,那麼如何為不同的業務邏輯來注入不同的bean呢,或者說外層的控制邏輯是什麼樣的,這裡我們就可以使用工廠方法模式了。所謂的工廠方法模式,就是定義一個工廠方法,通過傳入的引數,返回某個例項,然後通過該例項來處理後續的業務邏輯。一般的,工廠方法的返回值型別是一個介面型別,而選擇具體子類例項的邏輯則封裝到了工廠方法中了。通過這種方式,來將外層呼叫邏輯與具體的子類的獲取邏輯進行分離。如下圖展示了工廠方法模式的一個示意圖:
可以看到,工廠方法將具體例項的選擇進行了封裝,而客戶端,也就是我們的呼叫方只需要呼叫工廠的具體方法獲取到具體的事例即可,而不需要管具體的例項實現是什麼。上面我們講解了Spring中是如何使用策略模式宣告處理邏輯的,而沒有講如何選擇具體的策略,這裡我們就可以使用工廠方法模式。如下是我們宣告的一個PrizeSenderFactory
:
@Component
public class PrizeSenderFactory {
@Autowired
private List<PrizeSender> prizeSenders;
public PrizeSender getPrizeSender(SendPrizeRequest request) {
for (PrizeSender prizeSender : prizeSenders) {
if (prizeSender.support(request)) {
return prizeSender;
}
}
throw new UnsupportedOperationException("unsupported request: " + request);
}
}
這裡我們宣告一個了一個工廠方法getPrizeSender()
,其入參就是SendPrizeRequest
,而返回值是某個實現了PrizeSender
介面的例項,可以看到,通過這種方式,我們將具體的選擇方式下移到了具體的子類中的,因為當前實現了PrizeSender
的bean是否支援當前request的處理,是由具體的子類實現的。在該工廠方法中,我們也沒有任何與具體子類相關的邏輯,也就是說,該類實際上是可以動態檢測新加入的子類例項的。這主要是通過Spring的自動注入來實現的,主要是因為我們這裡注入的是一個List<PrizeSender>
,也就是說,如果有新的PrizeSender
的子類例項,只要其是Spring所管理的,那麼都會被注入到這裡來。下面就是我們編寫的一段用於測試的程式碼來模擬呼叫方的呼叫:
@Service
public class ApplicationService {
@Autowired
private PrizeSenderFactory prizeSenderFactory;
public void mockedClient() {
SendPrizeRequest request = new SendPrizeRequest();
request.setPrizeType(PrizeTypeEnum.POINT); // 這裡的request一般是根據資料庫或外部呼叫來生成的
PrizeSender prizeSender = prizeSenderFactory.getPrizeSender(request);
prizeSender.sendPrize(request);
}
}
在客戶端程式碼中,首先通過PrizeSenderFactory
獲取一個PrizeSender
例項,然後通過其sendPrize()
方法發放具體的獎勵,通過這種方式,將具體的獎勵發放邏輯與客戶端呼叫進行了解耦。而且根據前面的講解,我們也知道,如果新增了一種獎勵方式,我們只需要宣告一個新的實現了PrizeSender
的bean即可,而不需要對現有程式碼進行任何修改。
3. Builder模式
關於Builder模式,我想使用過lombok的同學肯定會說builder模式非常的簡單,只需要在某個bean上使用@Builder
註解進行宣告即可,lombok可以自動幫我們將其宣告為一個Builder的bean。關於這種使用方式,本人不置可否,不過就我的理解,這裡主要有兩個點我們需要理解:
- Builder模式就其名稱而言,是一個構建者,我更傾向於將其理解為通過一定的引數,通過一定的業務邏輯來最終生成某個物件。如果僅僅只是使用lombok的這種方式,其本質上也還是建立了一個簡單的bean,這個與通過getter和setter方式構建一個bean是沒有什麼大的區別的;
- 在Spring框架中,使用設計模式最大的問題在於如果在各個模式bean中能夠注入Spring的bean,如果能夠注入,那麼將大大的擴充套件其使用方式。因為我們就可以真的實現通過傳入的簡單的幾個引數,然後結合Spring注入的bean進行一定的處理後,以構造出我們所需要的某個bean。顯然,這是lombok所無法實現的;
關於Builder模式,我們可以以前面獎勵發放的SendPrizeRequest
的構造為例進行講解。在構造request物件的時候,必然是通過前臺傳如的某些引數來經過一定的處理,最後生成一個request物件。那麼我們就可以使用Builder模式來構建一個SendPrizeRequest
。這裡假設根據前臺呼叫,我們能夠獲取到prizeId和userId,那麼我們就可以建立一個如下的SendPrizeRequest
:
public class SendPrizeRequest {
private final PrizeTypeEnum prizeType;
private final int amount;
private final String userId;
public SendPrizeRequest(PrizeTypeEnum prizeType, int amount, String userId) {
this.prizeType = prizeType;
this.amount = amount;
this.userId = userId;
}
@Component
@Scope("prototype")
public static class Builder {
@Autowired
PrizeService prizeService;
private int prizeId;
private String userId;
public Builder prizeId(int prizeId) {
this.prizeId = prizeId;
return this;
}
public Builder userId(String userId) {
this.userId = userId;
return this;
}
public SendPrizeRequest build() {
Prize prize = prizeService.findById(prizeId);
return new SendPrizeRequest(prize.getPrizeType(), prize.getAmount(), userId);
}
}
public PrizeTypeEnum getPrizeType() {
return prizeType;
}
public int getAmount() {
return amount;
}
public String getUserId() {
return userId;
}
}
這裡就是使用Spring維護一個Builder模式的示例,具體的 維護方式就是在Builder類上使用@Component
和@Scope
註解來標註該Builder類,這樣我們就可以在Builder類中注入我們所需要的例項來進行一定的業務處理了。關於該模式,這裡有幾點需要說明:
- 在Builder類上必須使用
@Scope
註解來標註該例項為prototype
型別,因為很明顯,我們這裡的Builder例項是有狀態的,無法被多執行緒共享; - 在Builder.build()方法中,我們可以通過傳入的引數和注入的bean來進行一定的業務處理,從而得到構建一個
SendPrizeRequest
所需要的引數; - Builder類必須使用static修飾,因為在Java中,如果內部類不用static修飾,那麼該類的例項必須依賴於外部類的一個例項,而我們這裡本質上是希望通過內部類例項來構建外部類例項,也就是說內部類例項存在的時候,外部類例項是還不存在的,因而這裡必須使用static修飾;
- 根據標準的Builder模式的使用方式,外部類的各個引數都必須使用final修飾,然後只需要為其宣告getter方法即可。
上面我們展示瞭如何使用Spring的方式來宣告一個Builder模式的類,那麼我們該如何進行使用呢,如下是我們的一個使用示例:
@Service
public class ApplicationService {
@Autowired
private PrizeSenderFactory prizeSenderFactory;
@Autowired
private ApplicationContext context;
public void mockedClient() {
SendPrizeRequest request = newPrizeSendRequestBuilder()
.prizeId(1)
.userId("u4352234")
.build();
PrizeSender prizeSender = prizeSenderFactory.getPrizeSender(request);
prizeSender.sendPrize(request);
}
public Builder newPrizeSendRequestBuilder() {
return context.getBean(Builder.class);
}
}
上述程式碼中,我們主要要看一下newPrizeSendRequestBuilder()
方法,在Spring中,如果一個類是多例型別,也即使用@Scope("prototype")
進行了標註,那麼每次獲取該bean的時候就必須使用ApplicationContext.getBean()
方法獲取一個新的例項,至於具體的原因,讀者可查閱相關文件。我們這裡就是通過一個單獨的方法來建立一個Builder物件,然後通過流式來為其設定prizeId和userId等引數,最後通過build()方法構建得到了一個SendPrizeRequest
例項,通過該例項來進行後續的獎勵發放。
4. 小結
本文主要通過一個獎勵發放的示例來對Spring中如何使用工廠方法模式,策略模式和Builder模式的方式進行講解,並且著重強調了實現各個模式時我們所